【问题标题】:Handling multiple nested async operations in Bluebird (promises)在 Bluebird 中处理多个嵌套的异步操作(promises)
【发布时间】:2015-12-06 23:55:25
【问题描述】:

我对 JavaScript (Node.js) 和 Promises 还很陌生。我目前正在使用 AWS Lambda 和 DynamoDB。

我有一个从数据库中异步获取的项目列表(已使用 bluebird promise API 向 AWS SDK 承诺。)

对于这些项目中的每一个,我可能需要检索多个子项目(也是异步的),然后对于每个子项目,我必须执行另一个异步操作并确定此异步操作是否成功。

在为一个项目完成所有异步操作之后(即所有子项目的异步操作成功或失败),我需要更新数据库中项目的状态(失败/成功。)

这(下)是我目前所拥有的。你能告诉我我是否正确地做到了这一点?里面有没有逻辑错误?可以改进吗?

var doc = require('dynamodb-doc');
var promise = require("bluebird");

var dynamodb = new doc.DynamoDB();

var params = { /* ... */ };

promise.promisifyAll(Object.getPrototypeOf(dynamodb));

dynamodb.queryAsync(params)
    .then(function(data) {
        var items = data.Items;

        var promises = items.map(function(item) {
            params = { /* ...*/ };

            return dynamodb.queryAsync(params)
                .then(function(data2) {
                    var childItems = data2.Items;

                    var morePromises = childItems.map(function(device) {
                        return doAsyncWork()
                            .then(function success() {
                                console.log("Success!");
                            })
                            .catch(function error(err) {
                                console.log("Error!");
                            })
                    });

                    return promise.all(morePromises);
                })
                .then(function() {
                    // Update status for item in DB
                    params = { /* ...*/ };
                    return dynamodb.updateItemAsync(params);
                });
        });

        return promise.all(promises);
    })
    .then(function() {
        var response = { /* ... */ };

        context.succeed(response);
    })
    .catch(function(err) {
        context.fail(err);
    });

其他一些事情:

对于要完成的项目的所有异步操作,我使用 Promise.all(),从文档中我可以看到,即使一个承诺被拒绝,后续的承诺也会被拒绝。我不希望这种情况发生,即使一个承诺被拒绝,我也希望它继续下去。

同样,我最后使用 Promise.all() 等待所有项目完成处理的所有项目。如果一个失败了,其余的就不会被处理,对吧?我该如何克服这个问题?

有很多嵌套,我该如何改进这段代码?

我需要有一种方法来整合所有结果并将其作为响应传递,例如像这样:

{
    "Results": [{
        "ItemId": " ... ",
        "Status": "Success",
        "ChildItems": [{
            "ChildItemId": " ... ",
            "Status": "Success"
                /*  ...
                ...
                ...
            */
        }]
    }, {
        "ItemId": " ... ",
        "Status": "Error",
        "ChildItems": [{
            "ChildItemId": " ... ",
            "Status": "Success"
        }, {
            "ChildItemId": " ... ",
            "Status": "Error"
        }]
    }]
}

我想到的一个解决方案(可能有点难看)是在外部有一个全局对象,然后将结果存储在其中。有没有其他优雅的方式来做到这一点?

谢谢。

【问题讨论】:

    标签: javascript node.js asynchronous promise bluebird


    【解决方案1】:

    可以改进吗?

    使用 Bluebird 后您可以改进一些事情。

    使用 Bluebird 的 promise.map() 保存代码:

    您可以使用 Bluebird 的 promise.map(),而不是 array.map(),然后是 promise.all(),如下所示:

                   var childItems = data2.Items;
    
                    var morePromises = childItems.map(function(device) {
                        return doAsyncWork()
                            .then(function success() {
                                console.log("Success!");
                            })
                            .catch(function error(err) {
                                console.log("Error!");
                            })
                    });
    
                    return promise.all(morePromises);
    

    到这里:

                   return promise.map(data2.Items, function(item) {
                        // do something with item
                        return doAsyncWork();
                   });
    

    小心 .catch() 只记录日志的处理程序。

    如果你处理了一个 Promise 拒绝并且不重新抛出或返回一个被拒绝的 Promise,则 Promise 状态将从拒绝变为已完成。

    所以,当你像这里一样使用.catch() 时:

            return dynamodb.queryAsync(params)
                .then(function(data2) {
                    var childItems = data2.Items;
    
                    var morePromises = childItems.map(function(device) {
                        return doAsyncWork()
                            .then(function success() {
                                console.log("Success!");
                            })
                            .catch(function error(err) {
                                console.log("Error!");
                            })
                    });
    
                    return promise.all(morePromises);
                })
    

    这将“吃掉”来自doAsyncWork() 被拒绝承诺的任何错误。有时这就是你想要的(你想处理错误并继续,好像没有出错一样),但很多时候你需要出错才能以某种方式传播回来。您可以记录它,但通过重新抛出它让错误传播:

            return dynamodb.queryAsync(params)
                .then(function(data2) {
                    var childItems = data2.Items;
    
                    var morePromises = childItems.map(function(device) {
                        return doAsyncWork()
                            .then(function success() {
                                console.log("Success!");
                            })
                            .catch(function error(err) {
                                console.log("doAsyncWork Error: ", err);
                                //rethrow so error propagates
                                throw err;
                            })
                    });
    
                    return promise.all(morePromises);
                })
    

    为了完成我正在使用的项目的所有异步操作 Promise.all(),从文档中我可以看到,即使一个 承诺被拒绝,随后的承诺将被拒绝为 好。我不希望这种情况发生,我希望它继续下去,即使 单个承诺被拒绝。

    在 Bluebird 中,如果您不希望 Promise.all() 在一个 Promise 被拒绝时中止,如果您使用的是 Bluebird 2.x,则可以使用 Promise.settle() 而不是 Promise.all()。如果您使用的是 Bluebird 3.x,那么您在返回您的承诺时使用 .reflect()。如何做到这一点解释here in the Bluebirds docs。就个人而言,我喜欢 Promise.settle() 的工作方式,但一定有一些标准方向的原因来改变它。

    有很多嵌套,我该如何改进这段代码?

    您可以链接您正在做的一些事情,而不是嵌套。请参阅How to chain and share prior results with Promises 了解对多个操作进行排序的各种方法,而无需太多嵌套和累积结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-09-28
      • 2018-12-16
      • 1970-01-01
      • 1970-01-01
      • 2019-10-16
      相关资源
      最近更新 更多