如何使用承诺,以避免callback地狱?

所以我有一个Posts集合

{ id: String, comments: [String], # id of Comments links: [String], #id of Links } 

评论:{id:String,comment:String,}

链接:{id:string,链接:string,}

find一个post的评论和链接属于它的ID:

 Posts.findOne({id: id}, function(post) { Comments.find({id: post.id}, function(comments) { Links.find({id: post.id}, function(links) { res.json({post: post, comments: comment, links: links}) }) }) }) 

如何使用Promise( http://mongoosejs.com/docs/promises.html )避免callback地狱?

 var query = Posts.findOne({id: id}); var promise = query.exec(); promise.then(function (post) { var query1 = Comments.find({id: post.id}); var promise1 = query1.exec(); promise1.then(function(comments) { var query2 = Links.find({id: post.id}); var promise2 = query2.exec(); promise2.then(function(links) { res.json({post: post, comments: comment, links: links}) }) }) }); 

似乎没有好处……

试试这个:

 function getPost(id) { return Post .findOne({id: id}) .then( post => { return post; }); } 

使用Q模块

 function getCommentsAndLinks(post) { return Q.all([ Comment.find({id: post.id}), Links.find({id: post.id}) ]) .done( results => { let comments = results[0]; let links = results[1]; return [post, comments, links]; }) .catch( err => { // handle err }) 

在控制器上

 getPost(postId) .then(getCommentsAndLinks) .then( results => { let post = results[0]; let comments = results[1]; let links = results[2]; // more code here }) .catch( err => { // handle err }) 

但我build议你不要保存IDSstring,保存对象的实例,所以你可以使用填充来获取所有的评论和链接的数据,如下所示:

 Post .findOne({id: id}) .populate('comments') .populate('links') .then( post => { // here have the post with data of comments and links }); 

你在嵌套callback。 你不需要这样做。 如果你从那里返回一个承诺.then那么当你的承诺得到解决时.then你链接到它将得到解决:

 promise.then(post => Comments.find({id: post.id}) .then(comments => Links.find({id: post.id}) .then(links => {}); 

注释查询不依赖于链接,因此您可以一次执行两个查询:

 promise.then(post => { return Promise.all([ post, Comments.find({id: post.id}), Links.find({id: post.id}), ]); }).then(data => res.json({ post: data[0], comments: data[1], links: data[2], }); 

如果你使用蓝鸟这样的库,你也可以使用类似spread操作符的名字来使名字更加透明。


我也会考虑使用co来生成基于发生器的控制stream程,因为我认为这更清晰:

 co(function* () { const post = yield Posts.findOne({id}); const [comments, links] = yield [ Comments.find({id: post.id}), Links.find({id: post.id}), ]; res.json({post, comments, links}); }); 

你可以用这样的承诺来做到这一点:

 Posts.findOne({id: id}).exec().then(function(post) { let p1 = Comments.find({id: post.id}).exec(); let p2 = Links.find({id: post.id}).exec(); return Promise.all([p1, p2]).then(function(results) { res.json({post: post, comments: results[0], links: results[1]}); }); }).catch(function(err) { // error here }); 

这设置了两个操作Comments.find().exec()Links.find().exec() ,它们都依赖于postvariables,但是彼此独立,所以它们可以并行运行。 然后,它使用Promise.all()知道何时完成,然后可以输出JSON。

以下是一步一步的介绍:

  1. 运行Posts.findOne().exec()
  2. 完成后,同时启动Comments.find().exec()Links.find().exec()
  3. 使用Promise.all()知道两者何时完成。
  4. 当两者都完成后,输出JSON。

这可以使用较less的嵌套来完成,但是因为您在之后的请求中或在最终的JSON中使用了先前的结果,嵌套它会更容易一些。

你可以看到各种select,以分享先前的结果,而链接在这个答案的承诺请求如何链接和分享以前的结果 。


仅供参考,这个承诺实现真正闪耀的比你在你的问题中显示的是error handling。 您的非承诺代码显示没有error handling,但是承诺版本会将所有错误传播到您的.catch()处理程序。

使用promise的好处是你可以链接它们,所以你的代码可以简化为:

 let post, comments; Posts.findOne({id: id}).exec().then(_post => { post = _post; return Comments.find({id: post.id}).exec(); }).then(_comments => { comments = _comments; return Links.find({id: post.id}).exec(); }).then(links => res.json({post, comment, links})) .catch(error => res.error(error.message)); 

你会注意到我只需要一个catch块。

这是一个稍微更短的版本

 Posts.findOne({id: id}).then(function (post) { var query1 = Comments.find({id: post.id}); var query2 = Links.find({id: post.id}); Promise.all(query1.exec(), query2.exec()).then(function(data) { res.json({ post: post, comments: data[0], links: data[1] }); }); }); 

在我看来,你不能回避地狱。 这是asynchronous编程的本质。 你应该利用asynchronous编程,而不是试图使它看起来像同步。

您必须使用callback创build一个承诺,只是为了实现“then”语法。 “then”语法看起来更好,但是没有提供比callback更有用的东西,为什么呢? 承诺的唯一有用特征是Promise.all ,您可以使用它来等待所有的承诺完成。

尝试使用rxjs来处理asynchronous问题。 你仍然需要使用callback来创build一个rxjs的observable。 但rxjs提供了很多function来帮助您利用asynchronous编程,而不是避免它。