为什么JavaScript的'Promise.all`在失败条件下没有履行所有承诺?

时间:2017-02-17 17:50:24

标签: javascript promise es6-promise

根据MDN

  

如果任何传入的承诺拒绝,则所有承诺立即拒绝拒绝的承诺的价值,放弃所有其他承诺,无论他们是否已经解决。

ES6 spec似乎证实了这一点。

我的问题是:为什么Promise.all会拒绝承诺,如果其中任何一方拒绝,因为我希望它等待#34;所有"承诺解决,究竟做什么"丢弃"意思? (很难说出什么"丢弃"意味着在飞行中的承诺与可能尚未运行的承诺相比。)

我问,因为我经常遇到这样的情况:我有一份承诺列表,并希望等待他们所有解决并得到所有可能已经发生的拒绝, Promise.all无法满足的要求。相反,我必须使用这样的黑客:

const promises = []; // Array of promises
const settle = promise => promise.then(result => ({ result }), reason => ({ reason }));
Promise.all(promises.map(settle))
  .then(/ * check "reason" property in each element for rejection */);

3 个答案:

答案 0 :(得分:6)

与promises关联的异步操作全部运行。如果其中一个承诺拒绝,那么Promise.all()根本不会等待所有承诺完成,它会在第一个承诺拒绝时拒绝。这就是它的设计工作方式。如果您需要不同的逻辑(就像您希望等待所有这些逻辑完成,无论它们是否满足),那么您就不能仅使用Promise.all()

请记住,承诺不是异步操作本身。 promise只是一个跟踪异步操作状态的对象。因此,当您将一组承诺传递给Promise.all()时,所有这些异步操作都已经启动并且已经全部在飞行中。它们不会被停止或取消。

  

为什么Promise.all放弃了承诺,如果他们中的任何人拒绝,因为我希望它等待“所有”承诺解决。

它按照它的方式工作,因为它是如何设计的,当你不希望你的代码在任何类型的错误时继续时,这是一个非常常见的用例。如果碰巧不是你的用例,那么你需要使用.settle()的一些实现,它具有你想要的行为(你似乎已经知道了)。

我发现更有趣的问题是为什么规范和标准实现中没有.settle()选项,因为它也是一个相当常见的用例。幸运的是,正如您所发现的那样,创建自己的代码并不是很多。当我不需要实际的拒绝原因并且只想将一些指标值放入数组时,我经常使用这个相当简单的版本:

// settle all promises.  For rejeted promises, return a specific rejectVal that is
// distinguishable from your successful return values (often null or 0 or "" or {})
Promise.settleVal = function(rejectVal, promises) {
    return Promise.all(promises.map(function(p) {
        // make sure any values or foreign promises are wrapped in a promise
        return Promise.resolve(p).catch(function(err) {
            // instead of rejection, just return the rejectVal (often null or 0 or "" or {})
            return rejectVal;
        });
    }));
};

// sample usage:
Promise.settleVal(null, someArrayOfPromises).then(function(results) {
    results.forEach(function(r) {
        // log successful ones
        if (r !== null) {
           console.log(r);
        }
    });
});
  

“丢弃”究竟是什么意思?

这只意味着Promise.all()不再跟踪承诺。与它们相关联的异步操作始终保持正在执行的任何操作。事实上,如果这些承诺上有.then()个处理程序,它们就会像通常那样被调用。 discard在这里使用似乎是一个不幸的术语。除了Promise.all()停止关注它们之外什么也没发生。

仅供参考,如果我想要一个更强大的.settle()版本来跟踪所有结果并拒绝原因,那么我会使用它:

// ES6 version of settle that returns an instanceof Error for promises that rejected
Promise.settle = function(promises) {
    return Promise.all(promises.map(function(p) {
        // make sure any values or foreign promises are wrapped in a promise
        return Promise.resolve(p).catch(function(err) {
            // make sure error is wrapped in Error object so we can reliably detect which promises rejected
            if (err instanceof Error) {
                return err;
            } else {
                var errObject = new Error();
                errObject.rejectErr = err;
                return errObject;
            }
        });
    }));
}

// usage
Promise.settle(someArrayOfPromises).then(function(results) {
    results.forEach(function(r) {
       if (r instanceof Error) {
           console.log("reject reason", r.rejectErr);
       } else {
           // fulfilled value
           console.log("fulfilled value:", r);
       }
    });
});

这解析为一系列结果。如果结果是instanceof Error,则它被拒绝,否则它是一个已实现的值。

答案 1 :(得分:5)

因为Promise.all保证他们都成功了。就这么简单。

这是最有用的构建块以及Promise.race。其他一切都可以建立在那些之上。

没有settle,因为构建like this非常简单:

Promise.all([a(), b(), c()].map(p => p.catch(e => e)))

Promise.all之上构建settle没有简单的方法,这可能就是为什么它不是默认值。 settle还必须标准化区分成功值与错误的方法,这可能是主观的,取决于具体情况。

答案 2 :(得分:1)

我认为,因为拒绝承诺就像在同步代码中抛出错误一样,同步代码中的未捕获错误也会中断执行。

或者可以争辩的假设是,请求所有这些承诺并将它们组合成一个组合数组,然后等待所有这些完成意味着你需要它们全部继续你想要做的任何事情,如果其中一个失败了你的预期任务缺少一个依赖,并且简单地转发失败的原因是合乎逻辑的,直到这个原因以某种方式处理/捕获。