[关闭]
@greenfavo 2016-02-26T00:04:27.000000Z 字数 3200 阅读 1279

跳出js异步函数多层嵌套

js


异步回调的坑

最近在写nodejs,用mongodb操作数据的时候总会遇到查询多个数据,然后等查询完成后把结果一起输出。因为nodejs是异步执行的,所以查询结果并不是同时完成的,如果要一起输出结果,就要层层嵌套,但这样代码就变得很长很乱。比如下面这种在一个页面输出当前文章内容,评论,热门文章:

  1. var id=req.params.id;
  2. models.Article.find({_id:id},function(error,article){//查询文章
  3. if(error) return console.error(error);
  4. models.Comment.find({articleId:id},function(error,comments){//查询评论
  5. if(error) return console.error(error);
  6. models.Article.find({},function(error,hotPosts){//查询热门文章
  7. if(error) return console.error(error);
  8. res.render('article',{
  9. article:article,
  10. comment:comments,
  11. hotPost:hotPosts
  12. });
  13. }).limit(5).sort({views:-1});
  14. })
  15. });

这样写看着很混乱,不符合人类的线性思维。有没有办法让他们可以平行着写呢?当时想把它们分开写,用全局变量获得它们改变后的值,然后一起在渲染函数里调用全局变量。就像这样:

  1. var article;
  2. model.Article.find({_id:id},function(error,result){
  3. if (error) return console.error(error);
  4. article=result;
  5. });
  6. console.log(article);//undefined

上面代码会输出undefined,还是异步执行的坑,因为在输出article之前,数据库查询还没完成,因此还没完成赋值。难道就没有办法了吗?搜了半天终于在开源中国发现一个叫promise的东西,于是去搜promise,不是很理解。然后去看书,在《Nodejs实战》这本书里发现用nimble写很简单,它是个noejs的第三方包,用来解决异步流程控制问题。

nimble解决串行并行问题

在异步编程世界中,总会遇到有些步骤要按顺序执行,叫做串行;有些彼此没有关联不需要顺序执行,叫做并行。据说nimble不仅可以在nodejs中执行,还可以用于浏览器,不过我只在nodejs中用过。在nodejs中,首先要npm install nimble,然后这样写:

  1. var flow=require('nimble');
  2. var article,comments,hotPosts;
  3. flow.series([//串行
  4. function(callback){
  5. models.Article.find({_id:id},function(err,result){//查询文章
  6. if(err) ... //错误处理
  7. article=result;
  8. callback();//将控制权交给下一步
  9. });
  10. },
  11. function(callback){
  12. flow.parallel([//并行
  13. function(callback){//查询评论
  14. models.Comments.find({articleId:id},function(err,result){
  15. if(err)...
  16. comments=result;
  17. callback();
  18. });
  19. },
  20. models.Article.find({_id:id},function(err,result){//查询热门文章
  21. if(err) ... //错误处理
  22. hotPosts=result;
  23. callback();
  24. }).limt(5).sort({views:-1});
  25. ],callback);//把上一步的结果传过去
  26. },
  27. function(callback){//输出
  28. res.render('article',{
  29. article:article,
  30. comment:comments,
  31. hotPost:hotPosts
  32. });
  33. callback();
  34. }
  35. ]);

这段代码既包含了串行又包含了并行,是假设先查出当前文章内容,然后在查出评论和热门文章,然后一起输出结果。

promise解决方案

js的异步执行主要有setTimeout/setInterval,DOM事件模型和ajax,所以总是在回调函数中执行。假如有这样的回调嵌套:

  1. var a=1,b=2,c=3;
  2. (function(){//自执行函数
  3. setTimeout(function(){
  4. a=11;
  5. setTimeout(function(){
  6. b=22;
  7. setTimeout(function(){
  8. c=33;
  9. var sum=a+b+c;
  10. console.log(sum);//66
  11. },50);
  12. },50);
  13. },50);
  14. }());

这样写不仅看着混乱而且执行效率低,因为总是要等最底层的执行完才执行上一步。原生的promise可避免这种嵌套。
今天详细了解了一下promise的用法。其实,Promise就是一个类,而且这个类已经成为了ES6的标准,这个类目前在chrome32、Opera19、Firefox29以上的版本都已经支持了,要想在所有浏览器上都用上的话就看看es6-promise吧。用promise把上面嵌套改写一下:

  1. var a=1,b=2,c=3;
  2. function step1(resolve, reject){
  3. setTimeout(function(){
  4. if (a<1) {
  5. reject('出错了');//错误处理
  6. }else{
  7. a=11;
  8. resolve(a);//将值传递过去,必须有
  9. }
  10. },50);
  11. }
  12. function step2(resolve, reject){
  13. setTimeout(function(){
  14. b=22;
  15. resolve(b);
  16. },50);
  17. }
  18. function step3(resolve, reject){
  19. setTimeout(function(){
  20. c=33;
  21. resolve(c);
  22. },50);
  23. }
  24. new Promise(step1).then(function(){
  25. console.log(a);//11
  26. return new Promise(step2);
  27. }).then(function(){
  28. console.log(b);//22
  29. return new Promise(step3);
  30. }).then(function(){
  31. var sum=a+b+c;
  32. console.log(sum);//66
  33. });

用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更好用些。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注