ES6 Promise.all() 错误句柄 - 需要 .settle() 吗?

IT技术 javascript promise ecmascript-6 es6-promise
2021-01-13 15:55:24

假设我有一个Promise.all()处理两个Promise的。如果一个Promise产生错误,但另一个解决了,我希望能够在Promise.all()解决后根据情况处理错误

ES6 Promises 缺少结算方法,我假设是有充分理由的。但我不禁想到这种.settle()方法会让我的这个问题变得容易很多。

我是在用错误的方式来解决这个问题,还是在这里用一个解决方法来扩展 ES6 Promise是正确的?

我正在考虑如何使用的一个例子.settle()

Promise.all([Action1,Action2])
.settle(function(arrayOfSettledValues) 
    //if 1 failed but not 2, handle
    //if 2 failed but not 1, handle
    //etc....
)
1个回答

我是在用错误的方式来解决这个问题,还是在这里用一个解决方法来扩展 ES6 Promise是正确的?

您不能直接用于Promise.all()生成.settle()类型行为,无论是否有拒绝,都会为您提供所有结果,因为Promise.all()是“快速失败”并在第一个Promise拒绝后立即返回,并且它只返回拒绝原因,没有其他任何结果.

所以,需要一些不同的东西。很多时候,解决该问题的最简单方法是.then()向任何创建Promise数组的操作添加一个处理程序,以便它捕获任何拒绝并将它们转换为具有某些特定值的已履行Promise,您可以对其进行测试。但是,这种类型的解决方案取决于实现,因为它完全取决于您返回的值的类型,因此它并不完全通用。

如果你想要一个通用的解决方案,那么类似的东西.settle()非常有用。

您不能使用以下结构:

Promise.all([...]).settle(...).then(...);

注意(于 2019 年添加):似乎 Promise 标准工作已被Promise.allSettled()选为“类似定居”行为的标准实现。您可以在此答案的末尾看到更多相关信息。

因为Promise.all()当你通过它的第一个Promise拒绝时拒绝,它只返回那个拒绝。.settle()逻辑工作方式:

Promise.settle([...]).then(...);

而且,如果您有兴趣,这里有一个相当简单的实现Promise.settle()

// ES6 version of settle
Promise.settle = function(promises) {
    function PromiseInspection(fulfilled, val) {
        return {
            isFulfilled: function() {
                return fulfilled;
            }, isRejected: function() {
                return !fulfilled;
            }, isPending: function() {
                // PromiseInspection objects created here are never pending
                return false;
            }, value: function() {
                if (!fulfilled) {
                    throw new Error("Can't call .value() on a promise that is not fulfilled");
                }
                return val;
            }, reason: function() {
                if (fulfilled) {
                    throw new Error("Can't call .reason() on a promise that is fulfilled");
                }
                return val;
            }
        };
    }

    return Promise.all(promises.map(function(p) {
        // make sure any values are wrapped in a promise
        return Promise.resolve(p).then(function(val) {
            return new PromiseInspection(true, val);
        }, function(err) {
            return new PromiseInspection(false, err);
        });
    }));
}

在此实现中,Promise.settle()将始终解析(从不拒绝)并使用一组PromiseInspection对象进行解析,这些对象允许您测试每个单独的结果,以查看它是已解析还是被拒绝,以及每个结果的值或原因是什么。它的工作原理是.then()为传入的每个Promise附加一个处理程序,处理程序处理该Promise的解析或拒绝,并将结果放入一个PromiseInspection对象中,然后对象成为Promise的已解析值。

然后你会像这样使用这个实现;

Promise.settle([...]).then(function(results) {
    results.forEach(function(pi, index) {
        if (pi.isFulfilled()) {
            console.log("p[" + index + "] is fulfilled with value = ", pi.value());
        } else {
            console.log("p[" + index + "] is rejected with reasons = ", pi.reason());
        }
    });
});

仅供参考,我写了另一个版本的.settle我自己,.settleVal()当您不需要实际拒绝原因时,我经常发现它更容易使用,您只想知道给定的数组槽是否被拒绝。在此版本中,您传入一个默认值,该值应替换任何被拒绝的Promise。然后,您只会得到一个返回值的平面数组,以及任何设置为被拒绝的默认值的值。例如,你经常可以挑选rejectValnull0""{}它使结果更容易对付。这是函数:

// settle all promises.  For rejected 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).then(null, function(err) {
            // instead of rejection, just return the rejectVal (often null or 0 or "" or {})
            return rejectVal;
        });
    }));
};

然后,您可以像这样使用它:

Promise.settleVal(null, [...]).then(function(results) {
    results.forEach(function(pi, index) {
        if (pi !== null) {
            console.log("p[" + index + "] is fulfilled with value = ", pi);
        }
    });
});

这不是一个完整的替代品,.settle()因为有时您可能想知道它被拒绝的实际原因,或者您无法轻松区分拒绝值和非拒绝值。但是,我发现在 90% 以上的情况下,这更易于使用。


这是我最新的简化,在返回数组.settle()中留下一个instanceof Error作为区分已解析值和拒绝错误的方法:

// settle all promises.  For rejected promises, leave an Error object in the returned array
Promise.settleVal = 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) {
            let returnVal = err;
            // instead of rejection, leave the Error object in the array as the resolved value
            // make sure the err is wrapped in an Error object if not already an Error object
            if (!(err instanceof Error)) {
                returnVal = new Error();
                returnVal.data = err;
            }
            return returnVal;
        });
    }));
};

然后,您可以像这样使用它:

Promise.settleVal(null, [...]).then(function(results) {
    results.forEach(function(item, index) {
        if (item instanceof Error) {
            console.log("p[" + index + "] rejected with error = ", item);
        } else {
            console.log("p[" + index + "] fulfilled with value = ", item);
        }
    });
});

.settle()只要 aninstanceof Error永远不是您的Promise的已解决值(它真的不应该是),这可以完全替代所有情况


Promise标准努力

截至 2019 年,这似乎.allSettled()正在成为此类行为的标准。而且,这是一个 polyfill:

if (!Promise.allSettled) {
    Promise.allSettled = function(promises) {
        let wrappedPromises = Array.from(promises).map(p => 
             this.resolve(p).then(
                 val => ({ state: 'fulfilled', value: val }),
                 err => ({ state: 'rejected', reason: err })
             )
        );
        return this.all(wrappedPromises);
    }
}

用法如下:

let promises = [...];    // some array of promises, some of which may reject
Promise.allSettled(promises).then(results => {
    for (let r of results) {
        if (r.state === 'fulfilled') {
            console.log('fulfilled:', r.val);
        } else {
            console.log('rejected:', r.err);
        }
    }
});

请注意,Promise.allSettled()尽管后续.then()处理程序可能会抛出或返回一个被拒绝的Promise以使整个链拒绝,但本身总是会解决,永远不会拒绝。

截至 2019 年 6 月,当前桌面 Chrome 浏览器中尚未包含此功能,但计划在即将发布的版本中使用(例如 2019 年晚些时候)。

@Bergi - 是的,它是一个isThenable(). 为了提高效率,我试图避免在不需要时将Promise包装在Promise中,并且我试图尽可能接受任何 thenable,以防出现混合类型的Promise(例如 jQuery Promise)或其他一些Promise库)。
2021-03-28 15:55:24
你的isPromise功能更像是一个isThenable测试?然而,在settle一个真实的instanceof Promise(或只是总是调用Promise.resolve)似乎是合适的。
2021-03-30 15:55:24
实际上这正是Promise.resolve(p).then(…)它的作用:-) 如果p已经是 a Promise,则它不会被包装。
2021-04-01 15:55:24
@Elliot - 我添加了一个更简单的选项,可以在 90% 的时间内使用。
2021-04-02 15:55:24
@Bergi - 我更新了代码以始终调用Promise.resolve()和删除isPromise()调用。肯定更简单。
2021-04-07 15:55:24