Promise你把握不住?来看看异步转同步改造
Promise你把握不住?来看看异步转同步改造
前言
先说一下上一篇NodeJS文章
NodeJS 后端开发 06 连接MySQL
,这一篇展示了一个数据库链接的db.js 工具库。
该工具库提供了一个runSql函数,它的运行方式是先提交SQL,然后再异步把查询结果回调传递给callback函数的。
这个函数只适用于提前加载数据的情况,比如预先缓存批量的查库结果。然后用户请求WebAPI的时候,直接读查询缓存。
这样会有下面的问题:
- 新数据入库了,本地缓存需要更新。
- 而且无法支持动态查询。
说这么多,直接用Koa做个接口演示一下查询效果吧。
代码展示,KOA做接口查询数据库
const koa = require('Koa');
const app = new koa()
var router = require('koa-router')();
var db = require('./db-lite');
//做个简单接口
router.get('/', function(ctx, next){ctx.body='Hello, geeklevin!'});
//做个接口查询数据库
router.get('/query', function(ctx,next){
db.runSql("SELECT * FROM COMPANY; ", function(result,fields){
console.log('查询结果:', result);
ctx.body = JSON.stringify(result);
console.log('设置响应body成功!');
});
console.log('processing');
});
//启动服务器
app.use(router.routes());
const PORT = 8081
console.log('start app at port %s', PORT)
app.listen(PORT);
先查看简单接口展示效果:
再看看/query接口
日志看到设置了响应体ctx.body成功了,可这时候请求已经结束了。
因为db.runSql函数不是同步执行了,/query这个接口执行这个函数,碰到函数结束了,当前请求处理完毕,没有设置请求体,所以响应Not Found。
看看Promise吧
var data = new Promise(function(resolve, reject){
asynFunction(function(error, result){
if(error){
reject(error);
}else{
resolve(result);
}
});
});
async getData(){
var result = await data();
console.log('result is:', result);
}
就两段代码,第一段是构造Promise对象;第二段为调用Promise对象处理异步请求。
先说Promise。data是一个Promise对象,它内部调用了一个异步函数asynFunction(这个函数可以是查库,请求其他网站接口,消息队列获取数据等),但是它能够保证两个结果:
- 处理异步请求成功的时候调用resolve函数,传递result对象;
- 出错的时候调用reject函数,传递error对象。
就这么简单。
第二段代码就是一个声明了异步响应的函数,虽然内部调用的Promise对象,又套娃调用了异步函数,但是await语法糖,能够保证result变量赋值这一行同步执行,也就是getData会等待result赋值这一行异步调用执行完毕,再执行console.log。
这里稍微有点绕了,请读者停下来,好好思考整理一下。
简单来说就是,在async声明的异步函数中,同步等待Promise内部执行结果,就是,同步等待异步请求。
这样,异步转同步,很神奇吧!
像查询数据库这样的异步操作就能够在API中被等待,并响应给用户了,下面来代码了。
好,用koa接口得怎么做呢?需要在数据库调用端怎么调整呢?
下面是runSql的简化实现代码,我们要改造它
const runSql = function(sql, callback){
console.log('will run sql:', sql);
//这里是异步调用的,但是不是Promise实现,没有办法使用asyn,await来实现同步等待异步请求。
db.query(sql, function(error, result, fields){
try{
if(error) throw error;
console.log('query result:', result);
}finally{
if(callback) callback(result, fields);
}
});
}
先看koa怎么搞?
这个好办, 下面是一个简单的router(路由web API)。
我们看到在router.get 第二个参数加了async关键字修饰的方法。
根据ES6,我们知道async函数内可以使用await强制等待异步Promise函数的相应结果。
很明显,上面的runSql函数返回不是一个Promise对象的,所以这里先调用一个未实现的函数:prunSql。
router.get('/asynQuery', async function(ctx, next){
//参考上面Promise使用,声明同步等待一个返回Promise对象的函数。
var data = await prunSql('SELECT * FROM COMPANY;');
ctx.body = JSON.stringify(data)
});
这是一个新的接口, 跟其他接口不一样的就是,它处理请求的方法是用async声明的异步函数。
我们需要改造runSql函数
const prunSql = function(sql){
console.log('will run sql:', sql);
return new Promise(function(resolve, reject){
console.log('asyn run sql: ', sql);
db.query(sql, function(error, result, fields){
if(error){
console.log('[%s] asyn error:', error);
reject(error);
}else{
console.log('asyn result:', result);
resolve(result);
}
});
});
};
这就是改造好的prunSql,这里用了resolve函数把数据库查询结果传递出去。
这样在使用Promise的then可以获取到异步查询结果。
或者在一个异步响应的方法内await Promise对象,等待查询返回值。
看看使用Promise处理后的效果:
问题至此,迎刃而解!
具体用什么框架做接口不重要,理解整个思想才是重点。
贴一下代码, 下面的数据库查询库(db-lite.js),读者可自行更换成其他异步查询的库,进行改造,不必过多纠结一定要使用本文的runSql函数。
const koa = require('Koa');
const app = new koa()
var router = require('koa-router')();
var db = require('./db-lite');
router.get('/', function(ctx, next){ctx.body='Hello, geeklevin!'});
router.get('/query', function(ctx,next){
db.runSql("SELECT * FROM COMPANY; ", function(result,fields){
console.log('查询结果:', result);
ctx.body = JSON.stringify(result);
console.log('设置响应body成功!');
});
console.log('processing');
});
router.get('/asynQuery', async function(ctx, next){
var data = await db.prunSql('SELECT * FROM COMPANY;');
ctx.body = JSON.stringify(data)
});
app.use(router.routes());
const PORT = 8081
console.log('start app at port %s', PORT)
app.listen(PORT);
db-lite.js 代码:
const mysql = require('mysql');
const db = mysql.createConnection({
host: "localhost",
port: 3306,
user: "root",
password: "12345678"
});
const logDbStat = function(){
console.log("db state %s and threadID %s", db.state, db.threadId);
// console.log("db detail:", db);
}
logDbStat();
console.log('start to connect mysql');
db.connect(function(err){
if(err){
console.log('fail to connect db',err.stack);
throw err;
}
logDbStat();
});
const close = function(){
db.destroy();
console.log('db disconnected');
logDbStat();
};
var counter = 0;
const runSql = function(sql, callback){
counter++;
var cv = counter;
console.log('run sql[%s] :[%s]',cv, sql);
db.query(sql, function(error, result, fields){
try{
if(error) throw error;
}finally{
if(callback) callback(result, fields);
}
});
}
const prunSql = function(sql){
console.log('will sql:', sql);
return new Promise(function(resolve, reject){
counter++;
var cv = counter;
console.log('asyn run sql[%s] :[%s]', cv, sql);
db.query(sql, function(error, result, fields){
if(error){
console.log('[%s] asyn error:', cv, error);
reject(error);
}else{
// console.log('[%s] asyn result:', cv, result);
resolve(result);
}
});
});
};
const myDb = 'demo20210330';
runSql("USE " + myDb);
module.exports.runSql = runSql;
module.exports.prunSql = prunSql;
总结
Promise结合async和await结合能够产生神奇的效果,也是很多新手过不去的坎,希望读者都能够熟练运用。
上面展示代码,读者可以自行思考,改造工作中使用的异步请求转化为同步调用。
篇幅有限,当然这本质上还是异步执行的,希望读者自行鞭策,思考理解。
想学NodeJS的读者可以关注我的=> NodeJS专栏,
这对提高编程素养很有帮助,希望点赞转发,一键三连!
- 点赞
- 收藏
- 关注作者
评论(0)