ES6 Promise.all() 错误句柄 - 是不是需要 .settle()? [复制]

Posted

技术标签:

【中文标题】ES6 Promise.all() 错误句柄 - 是不是需要 .settle()? [复制]【英文标题】:ES6 Promise.all() error handle - Is .settle() needed? [duplicate]ES6 Promise.all() 错误句柄 - 是否需要 .settle()? [复制] 【发布时间】:2016-08-04 22:53:18 【问题描述】:

假设我有一个处理两个承诺的Promise.all()。如果一个承诺产生错误,但另一个承诺解决了,我希望能够在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() 是“快速失败”,并且在第一个承诺拒绝和它只返回拒绝原因,没有其他结果。

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

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

你不能使用结构:

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

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

因为Promise.all() 在您传递的第一个承诺被拒绝时拒绝并且它只返回该拒绝。 .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 的resolve 或reject,并将结果放入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。然后,您只得到一个返回值的平面数组,以及任何设置为默认值的被拒绝的值。例如,您通常可以从null0"" 中选择一个rejectVal,这样会使结果更容易处理。函数如下:

// 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() 在所有情况下,只要 instanceof Error 永远不是您的承诺的已解决值(它确实不应该是)。


承诺标准努力

截至 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() 处理程序可能会抛出或返回被拒绝的承诺以使整个链拒绝。

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

【讨论】:

您的isPromise 函数更像isThenable 测试?然而,在settle 中,一个真正的instanceof Promise(或者只是总是调用Promise.resolve)似乎是合适的。 @Bergi - 是的,它是isThenable()。出于效率的考虑,我试图避免在不需要时将承诺包装在承诺中,并且我试图尽可能地接受任何 thenable 以防出现混合类型的承诺(例如 jQuery 承诺或其他一些承诺库)。 实际上这正是Promise.resolve(p).then(…) 所做的:-) 如果p 已经是Promise,则它没有被包装。 @Bergi - 我将代码更新为始终调用 Promise.resolve() 并删除 isPromise() 调用。绝对更简单。 @Elliot - 我添加了一个更简单的选项,可以在 90% 的时间内使用。

以上是关于ES6 Promise.all() 错误句柄 - 是不是需要 .settle()? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

使用 ES6 的 Promise.all() 时限制并发的最佳方法是啥?

ES6(十三)Promise

es6中promise ALL Race Resolve Reject finish的实现

利用ES6的Promise.all实现至少请求多长时间

ES6 Promise Javascript

带你快速入门ES6中的Promise对象