【问题标题】:How do I use promises in node.js to clean up a chain of callbacks?如何在 node.js 中使用 Promise 来清理回调链?
【发布时间】:2015-07-26 19:09:46
【问题描述】:

我试图弄清楚如何使用 Promise,尤其是 Q 实现来清理 node.js 程序中一些混乱的嵌套回调。不幸的是,似乎很少有简单的例子可以说明我想做的事情。

这是我现在拥有的嵌套回调的简化版本:

    var parent = this;
    this.receiveMessage(params, function(err, request) {
    if (err) console.log(err, err.stack);
    else {
       parent.handleMessage(request, function(response) {
           parent.sendMessage(JSON.stringify(response), function() {
               console.log("response sent");
               var params = { ReceiptHandle:request.Messages[0].ReceiptHandle };
               parent.deleteMessage(params, function() {
                   parent.waitForMessage();
               });
           });
       });
    }
});

如您所见,这非常混乱,并且有 4 级嵌套回调。

使用 Q,我发现您可以从以下内容开始:

Q.nfcall(this.connection.receiveMessage, params)
    .then(function(err, request) {
        return(Q.nfcall(this.handleMessage(request));
    })
    .then(function(response)) { 
        return(Q.nfcall(this.sendMessage(JSON.stringify(response))));
    } ...

等等......但这似乎不太正确。首先,我需要对链中的每个函数调用 Q.nfcall 吗?另外,我是否可以避免使用回调时遇到的“this”问题?我是否以正确的方式使用承诺?

【问题讨论】:

    标签: javascript node.js callback promise q


    【解决方案1】:

    我也遇到过类似的问题,后来我意识到这是因为 Q。在我看来,Q 的 API 很混乱,而且使用很少的简单示例会很麻烦。我建议尝试任何其他库,尽管我确实推荐 Bluebird。使用 Bluebird,您可以执行以下操作:

    var Promise = require('bluebird');
    var parent = this;
    Promise.promisifyAll(parent, { suffix: "P" });
    parent.receiveMessageP(params)
      .then(function (request) {
        return [request, parent.handleMessageP(request)];
      })
      .spread(function (request, response) {
        return [request, parent.sendMessageP(JSON.stringify(response))];
      })
      .spread(function (request) {
        console.log("response sent");
        var params = { ReceiptHandle: request.Messages[0].ReceiptHandle };
        return parent.deleteMessageP(params);
      })
      .then(function () {
        parent.waitForMessage();
      })
      .catch(function (err) {
        console.log(err, err.stack);
      });
    

    如果你不喜欢返回数组的风格和使用.spread,你可以在你的外部范围内使用一个映射对象。

    var Promise = require('bluebird');
    var parent  = this;
    Promise.promisifyAll(parent, { suffix: "P" });
    var cache = {};
    parent.receiveMessageP(params)
      .then(function (request) {
        cache.request = request;
        return parent.handleMessageP(request);
      })
      .then(function (response) {
        return parent.sendMessageP(JSON.stringify(response));
      })
      .then(function () {
        console.log("response sent");
        var params = { ReceiptHandle: cache.request.Messages[0].ReceiptHandle };
        return parent.deleteMessageP(params);
      })
      .then(function () {
        parent.waitForMessage();
      })
      .catch(function (err) {
        console.log(err, err.stack);
      });
    

    如果您需要访问链中较早解决的变量,您可以简单地将它们添加到cache 对象以便于访问。有时,如果你有很多这些,这种方法更干净,更容易阅读。在大多数情况下,我通常更喜欢第一个示例,只是为了避免污染父作用域并可能保留应以其他方式处理的引用。

    并不是说你不能在 Q 中做类似于 promisifyAll 的事情,但 Bluebird 更高效、更直观。

    如果您的回调不符合 function (err, successValue) 的典型节点样式签名(您的一些似乎不符合,这意味着 promisifyAll 将无法处理它们),那么您可以定义一个自定义 "蓝鸟中的承诺者。要么修改你的回调 API 以符合node-style callbacks

    https://github.com/petkaantonov/bluebird/blob/master/API.md#option-promisifier

    【讨论】:

    • 请注意,handleMessagesendMessagedeleteMessage 实际上并不遵守节点回调约定
    • 我确实注意到了这一点,但它们显然是他的应用程序中的自定义方法。他很可能能够“节点化”他自己的 API。无论如何,即使不是,我的回答的重点是提供 Q 的替代方案,因为它的 API 更直观且文档更好。我确信这个答案是一个足够好的开始。 Bluebird 的 promisifyAll 方法支持自定义 promisify 函数。
    • 嗯,如果是他自己的方法,他最好答应他们:-)
    • 哎呀,你是对的。但他的代码显然不能工作:-)
    • 不,不是我的应用程序中的自定义方法。我忽略了 err 对象,因为我不知道它是必需的,并且我尝试提供一个简化的示例,不会造成混乱。
    【解决方案2】:

    Promise 确实可以让您取消嵌套回调,但应用 nfcall 内联是很麻烦的。您可以将nbind 用作原始函数的装饰器,因此您可以将其用作承诺返回函数来构建链:

    obj.receiveMessage = Q.nbind(obj.receiveMessage, obj);
    obj.deleteMessage = ...
    

    现在读起来会更好:

    this.receiveMessage(params)
      .then(function(request) {
        return parent.handleMessage(request);
      })
      .then(function(response) {
        var params = {ReceiptHandle: request.Messages[0].ReceiptHandle};
        return parent.deleteMessage(params);
      })
      .then(parent.waitForMessage)
      .catch(function(err) {
        console.log(err, err.stack);
      });
    

    【讨论】:

    • 请注意,handleMessagesendMessagedeleteMessage 实际上并不遵守节点回调约定
    • 还要注意,在第二个 then 处理程序中对 request.Messages[0] 的调用将导致 Cannot call Messages[0] of undefined。您需要通过从第一个和第二个处理程序返回一个数组来将request 向下传递,并使用spread 而不是then 将数组的结果传播到链中下一个处理程序的多个参数中。例如,请参阅我的答案; Q有同样的能力。
    • 他们怎么不遵守节点回调约定?是因为他们都必须犯错误和数据吗?如果是这样的话,好吧,他们确实这样做了,但为了简单起见,我没有把 err 对象放在这里。我不知道这是节点的要求。
    猜你喜欢
    • 1970-01-01
    • 2016-02-09
    • 2014-05-10
    • 2015-04-10
    • 2016-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多