【问题标题】:How to use promise to avoid callback hell? [duplicate]如何使用promise避免回调地狱? [复制]
【发布时间】:2016-06-24 15:42:43
【问题描述】:

所以我有一个帖子集合

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

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

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

用 id 找到一个带有 cmets 和链接的帖子:

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

好像不好......

【问题讨论】:

    标签: javascript node.js mongoose promise


    【解决方案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。

    这可以通过较少的嵌套来完成,但是因为您在后续请求或最终 JSON 中使用先前的结果,所以稍微嵌套它会更容易。

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


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

    【讨论】:

      【解决方案2】:

      使用 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 块。

      【讨论】:

      【解决方案3】:

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

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

      cmets 查询不依赖于链接,因此您实际上可以同时执行两个查询:

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

      【讨论】:

      • 通过将 post 变量传递到下一个 .then() 处理程序的巧妙方法将其传递给 Promise.all()
      【解决方案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来获取cmets和links的所有数据,像这样:

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

      【讨论】:

      • @Sato - 这里的第一个版本序列化了getComments()getLinks(),这是不必要的。并行运行它们(如我的回答)可能会表现得更好。此外,请确保您了解如何在 non-promise 或 Promise 版本中进行错误处理。
      • @jfriend00 你是对的,我使用Q 模块编辑了我的答案,如果你愿意,你也可以使用Promise.all()
      【解决方案5】:

      这是一个略短的版本

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

      【讨论】:

      • 这和我的回答差不多。
      • 是的,抱歉,当我开始回答时,您的回答尚未发布。我是在我发布后才看到的。
      【解决方案6】:

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

      你必须使用回调来创建一个promise,只是为了实现“then”的语法。 “then”语法看起来更好,但实际上并没有提供比回调有用的任何东西,何必呢。 Promise 唯一有用的功能是 Promise.all,您可以使用它来等待所有的 Promise 完成。

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

      【讨论】:

      • 上面的回答表明,用 Promise 避免回调地狱是可能的,也是可取的。查看@Explosion Pills 的答案
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-02-09
      • 2017-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-18
      • 1970-01-01
      相关资源
      最近更新 更多