如何使用诺言避免回调地狱?

时间:2016-03-10 00:50:12

标签: javascript node.js mongoose promise

所以我有一个帖子集合

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

评论:     {       id:String,       评论:字符串,     }

链接:     {       id:String,       链接:字符串,     }

通过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)来避免回调地狱?

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})
    })
  })
});

似乎没有好处......

6 个答案:

答案 0 :(得分:4)

尝试使用此功能:

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
}) 

但我建议您不要保存IDS字符串,保存对象的实例,这样您就可以使用populate来获取评论和链接的所有数据,如下所示:

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

答案 1 :(得分:2)

您正在嵌套回调。你不需要这样做。如果您从.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],
});

如果您使用像bluebird这样的库,您还可以使用类似spread运算符的内容来使名称更加透明。

我还会考虑将co用于基于生成器的控制流,因为我认为这更加清晰:

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});
});

答案 2 :(得分:1)

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

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(),它们都依赖于post变量,但彼此独立,因此它们可以并行运行。然后,它使用Promise.all()知道两者何时完成,然后输出JSON。

以下是逐步说明:

  1. 运行Posts.findOne().exec()
  2. 完成后,同时启动Comments.find().exec()Links.find().exec()
  3. 使用Promise.all()了解两者何时完成。
  4. 当两者都完成后,输出JSON。
  5. 这可以通过较少的嵌套来完成,但由于您在后续请求或最终JSON中使用先前结果,因此更容易嵌套它。

    在另一个答案How to chain and share prior results中链接承诺请求时,您可以看到分享先前结果的各种选项。

    仅供参考,与你在问题中显示的内容相比,这个承诺实现真的很好用于错误处理。您的非承诺代码没有显示错误处理,但promise版本会将所有错误传播到.catch()处理程序。

答案 3 :(得分:1)

使用promises的优点是可以链接它们,因此您的代码可以简化为:

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));

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

答案 4 :(得分:1)

这是一个更短的版本

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] });
  });
});

答案 5 :(得分:-3)

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

你必须使用回调来创建一个承诺,只是为了实现"然后"句法。 "然后"语法看起来更好,但没有真正提供任何有用的回调,为什么这么麻烦。承诺唯一有用的功能是$('div').html('one').delay(5000).html('two'); ,您可以用它来等待所有承诺的完成。

尝试使用rxjs来处理异步问题。您仍然必须使用回调来创建rxjs observable。但rxjs提供了许多功能来帮助您利用异步编程,而不是避免它。