@greenfavo
2016-02-25T16:04:27.000000Z
字数 3200
阅读 1374
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);//11return new Promise(step2);}).then(function(){console.log(b);//22return 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更好用些。