等待多个承诺被拒绝

3

进行一些测试,我需要等待几个Promise被拒绝。

我知道可以使用jQuery.when()来等待多个Promise被解决。但是这种方法会在其中一个Promise失败时拒绝主Promise。

我知道所有的Promise都会失败,但我仍然需要等待。我该如何做呢?

以伪代码的形式,我想要做的是:

var promise1 = connection.doCall();
var promise2 = connection.doCall();

$.when([promise1, promise2]).allOfThemFail(function() {
    assertThatSomeProcessWasDoneOnlyOnce()
});

$.when 不接受数组? - Bergi
如果你的承诺中有一个没有失败,你希望承诺链走成功路径还是错误路径? - Roamer-1888
2个回答

4
使用Bluebird Promise库,您可以使用Promise.settle()函数,该函数等待所有承诺被解决(例如,履行或拒绝),然后您可以简单地查询列表以查看它们是否全部被拒绝。
var p1 = connection.doCall();
var p2 = connection.doCall();
Promise.settle([p1, p2]).then(function(results) {
    var allRejected = results.every(function(item) {
        return item.isRejected();
    });
    // act on allRejected here
});

这是一个针对jQuery的settle()版本:
(function() {    

    function isPromise(p) {
        return p && (typeof p === "object" || typeof p === "function") && typeof p.then === "function";
    }

    function wrapInPromise(p) {
        if (!isPromise(p)) {
            p = $.Deferred().resolve(p);
        }
        return p;
    }

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

    // pass either multiple promises as separate arguments or an array of promises
    $.settle = function(p1) {
        var args;
        if (Array.isArray(p1)) {
              args = p1;
        } else {
            args = Array.prototype.slice.call(arguments);
        }

        return $.when.apply($, args.map(function(p) {
            // make sure p is a promise (it could be just a value)
            p = wrapInPromise(p);
            // Now we know for sure that p is a promise
            // Make sure that the returned promise here is always resolved with a PromiseInspection object, never rejected
            return p.then(function(val) {
                return new PromiseInspection(true, val);
            }, function(reason) {
                // convert rejected promise into resolved promise by returning a resolved promised
                // One could just return the promiseInspection object directly if jQuery was
                // Promise spec compliant, but jQuery 1.x and 2.x are not so we have to take this extra step
                return wrapInPromise(new PromiseInspection(false, reason));
            });
        })).then(function() {
              // return an array of results which is just more convenient to work with
              // than the separate arguments that $.when() would normally return
            return Array.prototype.slice.call(arguments);
        });
    }

})();

而且,您可以类似于Bluebird解决方案来使用这个:

var p1 = connection.doCall();
var p2 = connection.doCall();
$.settle([p1, p2]).then(function(results) {
    var allRejected = results.every(function(item) {
        return item.isRejected();
    });
    // act on allRejected here
});

而且,这里有一个使用标准ES6 promises从头构建的settle()类型函数。它返回一个单一的promise,当所有其他promises完成时解决(不考虑它们的最终状态)。这个超级promise的解决结果是一个PromiseInspection对象数组,您可以查询isFulfilled()isRejected()并可以获得.value().reason()
Promise.isPromise = function(p) {
    return p && (typeof p === "object" || typeof p === "function") && typeof p.then === "function";
}

// 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
        if (!Promise.isPromise(p)) {
            p = Promise.resolve(p);
        }
        return p.then(function(val) {
            return new PromiseInspection(true, val);
        }, function(err) {
            return new PromiseInspection(false, val);
        });
    }));
}

而且,您可以像上面那样使用它:

var p1 = connection.doCall();
var p2 = connection.doCall();
Promise.settle([p1, p2]).then(function(results) {
    var allRejected = results.every(function(item) {
        return item.isRejected();
    });
    // act on allRejected here
});

谢谢推荐Bluebird,这是一个非常优秀的库。 另外对于使用ES6 Promises实现的Settle功能点给个赞!@aron-p-johny的回答值得注意,因为它使用了jQuery promises进行实现,不依赖其他库,并且没有使用几乎过时(在2015年中期)的特性。 - Fernando
对于长度为零的承诺数组或传递给该数组的非承诺(例如仅有值)情况进行了一些编辑。 - jfriend00
添加了针对jQuery的特定版本$.settle() - jfriend00
更新标准Promise实现,使其与另外两个实现完全相同,并在内部使用Promise.all()来完成其工作,而不是编写自己的Promise跟踪逻辑。 - jfriend00

1
我认为这方面没有内置函数,但是您可以很容易地实现。
var promise1 = connection.doCall();
var promise2 = connection.doCall();

allOfThemFail([promise1, promise2]).always(function () {
    assertThatSomeProcessWasDoneOnlyOnce()
});


function allOfThemFail(array) {
    var count = 0;
    len = array.length, failed = false, deferred = $.Deferred();

    $.each(array, function (i, item) {
        item.fail(fail).always(always);
    })


    function always() {
        if (++count == len) {
            if (failed) {
                deferred.reject();
            } else {
                deferred.resolve();
            }
        }
    }

    function fail() {
        failed = true;
    }

    return deferred.promise();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接