为什么 JavaScript 的 `Promise.all` 不会在失败条件下运行所有​​承诺? [英] Why does JavaScript's `Promise.all` not run all promises in failure conditions?

查看:18
本文介绍了为什么 JavaScript 的 `Promise.all` 不会在失败条件下运行所有​​承诺?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据 MDN:

<块引用>

如果任何传入的 promise 被拒绝,则 all Promise 会立即拒绝并使用被拒绝的 Promise 的值,丢弃所有其他 Promise,无论它们是否已解决.

ES6 规范似乎证实了这一点.

我的问题是:为什么 Promise.all 会在任何一个拒绝承诺时丢弃承诺,因为我希望它等待所有"承诺解决,以及丢弃"究竟是什么意思?(很难区分丢弃"对于正在进行的承诺与可能尚未运行的承诺意味着什么.)

我问是因为我经常遇到这样的情况,我有一个承诺列表,并希望等待它们all解决并获得可能发生的所有拒绝,Promise.all 不适合.相反,我必须使用这样的黑客:

const promises = [];//承诺数组常量结算 = 承诺 =>promise.then(result => ({ result }), reason => ({ reason }));Promise.all(promises.map(settle)).then(/* 检查每个元素中的reason"属性是否被拒绝 */);

解决方案

与 promise 相关的异步操作全部运行.如果其中一个 promise 被拒绝,那么 Promise.all() 不会等待所有这些 promise 完成,而是在第一个 promise 被拒绝时拒绝.这就是它的设计工作方式.如果您需要不同的逻辑(比如您想等待所有这些都完成,无论它们是满足还是拒绝),那么您不能只使用 Promise.all().>

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

<块引用>

如果 Promise.all 中的任何一个拒绝,为什么 Promise.all 会丢弃承诺,因为我希望它等待所有"承诺解决.

它的工作方式是这样的,因为这就是它的设计方式,这是一个非常常见的用例,如果出现任何类型的错误,您不希望代码继续运行.如果它恰好不是您的用例,那么您需要使用 .settle() 的一些实现,它具有您想要的行为(您似乎已经知道).

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

//解决所有的承诺.对于被拒绝的承诺,返回一个特定的 rejectVal//与您成功的返回值区分开来(通常为 null 或 0 或 "" 或 {})Promise.settleVal = function(rejectVal, promises) {返回 Promise.all(promises.map(function(p) {//确保任何值或外部承诺都包含在承诺中返回 Promise.resolve(p).catch(function(err) {//而不是拒绝,只需返回rejectVal(通常为null或0或"或{})返回拒绝值;});}));};//示例用法:Promise.settleVal(null, someArrayOfPromises).then(function(results) {结果.forEach(函数(r){//记录成功的如果(r !== 空){控制台.log(r);}});});

<块引用>

丢弃"究竟是什么意思?

这只是意味着承诺不再被 Promise.all() 跟踪.他们所关联的异步操作会继续做他们打算做的事情.而且,事实上,如果这些 Promise 具有 .then() 处理程序,它们将像往常一样被调用.discard 在这里使用似乎是一个不幸的术语.除了 Promise.all() 不再关注它们之外,什么也没有发生.

<小时>

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

//ES6 版本的结算,为被拒绝的承诺返回一个错误实例Promise.settle = 函数(承诺){返回 Promise.all(promises.map(function(p) {//确保任何值或外部承诺都包含在承诺中返回 Promise.resolve(p).catch(function(err) {//确保错误包含在 Error 对象中,以便我们可以可靠地检测哪些承诺被拒绝如果(错误实例错误){返回错误;} 别的 {var errObject = new Error();errObject.rejectErr = 错误;返回错误对象;}});}));}//用法Promise.settle(someArrayOfPromises).then(function(results) {结果.forEach(函数(r){if (r instanceof Error) {console.log("拒绝原因", r.rejectErr);} 别的 {//满足值console.log("完成值:", r);}});});

这将解析为一系列结果.如果结果为 instanceof Error,则为拒绝,否则为满足值.

According to MDN:

If any of the passed in promises rejects, the all Promise immediately rejects with the value of the promise that rejected, discarding all the other promises whether or not they have resolved.

The ES6 spec seems to confirm this.

My question is: Why does Promise.all discard promises if any of them reject, since I would expect it to wait for "all" promises to settle, and what exactly does "discard" mean? (It's hard to tell what "discard" means for in-flight promises vs. promises that may not have run yet.)

I ask because I frequently run into situations where I have a list of promises and want to wait for them all to settle and get all rejections that may have occurred, which Promise.all doesn't cater to. Instead, I have to use a hack like this:

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

解决方案

The asynchronous operations associated with the promises are all run. If one of those promises rejects, then Promise.all() simply does not wait for all of them to complete, it rejects when the first promise rejects. That is just how it was designed to work. If you need different logic (like you want to wait for all of them to be done, no matter whether they fulfill or reject), then you can't use just Promise.all().

Remember, a promise is not the async operation itself. A promise is just an object that keeps track of the state of the async operation. So, when you pass an array of promises to Promise.all(), all those async operations have already been started and are all in-flight already. They won't be stopped or cancelled.

Why does Promise.all discard promises if any of them reject, since I would expect it to wait for "all" promises to settle.

It works the way it does because that's how it was designed and that is a very common use case when you don't want your code to continue if there was any sort of error. If it happens to not be your use case, then you need to use some implementation of .settle() which has the behavior you want (which you seem to already know).

What I find the more interesting question is why is there not a .settle() option in the specification and standard implementation since it is also a fairly common use case. Fortunately, as you have found, it is not a lot of code to make your own. When I don't need the actual reject reason and just want some indicator value to be placed into the array, I often use this fairly simple to use version:

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

what exactly does "discard" mean?

It just means that the promises are no longer tracked by Promise.all(). The async operations they are associated with keep right on doing whatever they were going to do. And, in fact if those promises have .then() handlers on them, they will be called just as they normally would. discard does seem like an unfortunate term to use here. Nothing happens other than Promise.all() stops paying attention to them.


FYI, if I want a more robust version of .settle() that keeps track of all results and reject reasons, then I use this:

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

This resolves to an array of results. If a result is instanceof Error, then it was a rejected, otherwise it's a fulfilled value.

这篇关于为什么 JavaScript 的 `Promise.all` 不会在失败条件下运行所有​​承诺?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆