@greenfavo
2016-02-26T00:04:27.000000Z
字数 3200
阅读 1279
js
最近在写nodejs,用mongodb操作数据的时候总会遇到查询多个数据,然后等查询完成后把结果一起输出。因为nodejs是异步执行的,所以查询结果并不是同时完成的,如果要一起输出结果,就要层层嵌套,但这样代码就变得很长很乱。比如下面这种在一个页面输出当前文章内容,评论,热门文章:
var id=req.params.id;
models.Article.find({_id:id},function(error,article){//查询文章
if(error) return console.error(error);
models.Comment.find({articleId:id},function(error,comments){//查询评论
if(error) return console.error(error);
models.Article.find({},function(error,hotPosts){//查询热门文章
if(error) return console.error(error);
res.render('article',{
article:article,
comment:comments,
hotPost:hotPosts
});
}).limit(5).sort({views:-1});
})
});
这样写看着很混乱,不符合人类的线性思维。有没有办法让他们可以平行着写呢?当时想把它们分开写,用全局变量获得它们改变后的值,然后一起在渲染函数里调用全局变量。就像这样:
var article;
model.Article.find({_id:id},function(error,result){
if (error) return console.error(error);
article=result;
});
console.log(article);//undefined
上面代码会输出undefined,还是异步执行的坑,因为在输出article之前,数据库查询还没完成,因此还没完成赋值。难道就没有办法了吗?搜了半天终于在开源中国发现一个叫promise的东西,于是去搜promise,不是很理解。然后去看书,在《Nodejs实战》这本书里发现用nimble写很简单,它是个noejs的第三方包,用来解决异步流程控制问题。
在异步编程世界中,总会遇到有些步骤要按顺序执行,叫做串行;有些彼此没有关联不需要顺序执行,叫做并行。据说nimble不仅可以在nodejs中执行,还可以用于浏览器,不过我只在nodejs中用过。在nodejs中,首先要npm install nimble,然后这样写:
var flow=require('nimble');
var article,comments,hotPosts;
flow.series([//串行
function(callback){
models.Article.find({_id:id},function(err,result){//查询文章
if(err) ... //错误处理
article=result;
callback();//将控制权交给下一步
});
},
function(callback){
flow.parallel([//并行
function(callback){//查询评论
models.Comments.find({articleId:id},function(err,result){
if(err)...
comments=result;
callback();
});
},
models.Article.find({_id:id},function(err,result){//查询热门文章
if(err) ... //错误处理
hotPosts=result;
callback();
}).limt(5).sort({views:-1});
],callback);//把上一步的结果传过去
},
function(callback){//输出
res.render('article',{
article:article,
comment:comments,
hotPost:hotPosts
});
callback();
}
]);
这段代码既包含了串行又包含了并行,是假设先查出当前文章内容,然后在查出评论和热门文章,然后一起输出结果。
js的异步执行主要有setTimeout/setInterval,DOM事件模型和ajax,所以总是在回调函数中执行。假如有这样的回调嵌套:
var a=1,b=2,c=3;
(function(){//自执行函数
setTimeout(function(){
a=11;
setTimeout(function(){
b=22;
setTimeout(function(){
c=33;
var sum=a+b+c;
console.log(sum);//66
},50);
},50);
},50);
}());
这样写不仅看着混乱而且执行效率低,因为总是要等最底层的执行完才执行上一步。原生的promise可避免这种嵌套。
今天详细了解了一下promise的用法。其实,Promise就是一个类,而且这个类已经成为了ES6的标准,这个类目前在chrome32、Opera19、Firefox29以上的版本都已经支持了,要想在所有浏览器上都用上的话就看看es6-promise吧。用promise把上面嵌套改写一下:
var a=1,b=2,c=3;
function step1(resolve, reject){
setTimeout(function(){
if (a<1) {
reject('出错了');//错误处理
}else{
a=11;
resolve(a);//将值传递过去,必须有
}
},50);
}
function step2(resolve, reject){
setTimeout(function(){
b=22;
resolve(b);
},50);
}
function step3(resolve, reject){
setTimeout(function(){
c=33;
resolve(c);
},50);
}
new Promise(step1).then(function(){
console.log(a);//11
return new Promise(step2);
}).then(function(){
console.log(b);//22
return new Promise(step3);
}).then(function(){
var sum=a+b+c;
console.log(sum);//66
});
用promise就可以把每个异步函数单独写,逻辑清晰了很多。在nodejs中也可以用。
promise的主要用法就是将各个异步操作封装成好多Promise,而一个Promise只处理一个异步逻辑。最后将各个Promise用链式调用写法串联,在这样处理下,如果异步逻辑之间前后关系很重的话,你也不需要层层嵌套,只需要把每个异步逻辑封装成Promise链式调用就可以了。
Promise构造函数只接受一个参数,即带有异步逻辑的函数。这个函数在 new Promise 时已经执行了。只不过在没有调用 then 之前不会 resolve 或 reject。
在then方法中通常传递两个参数,一个 resolve 函数,一个 reject 函数。reject就是出错的时候运行的函数。resolve 函数必须返回一个值才能把链式调用进行下去。
其实还有一种解决方法,ES6的generator,nodejs从v0.11.2开始已经就支持了generator,不过我还有了解过,暂且不说。相比之下,还是原生的promise更好用些。