假设我有一组promise正在发出网络请求,其中一个将失败:

// http://does-not-exist will throw a TypeError
var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr)
  .then(res => console.log('success', res))
  .catch(err => console.log('error', err)) // This is executed   

假设我想要等到所有这些都完成,不管是否有一个失败了。可能有一个资源的网络错误,我可以没有,但如果我能得到,我想在继续之前。我想优雅地处理网络故障。

因为承诺。所有这些都没有留下任何空间,在不使用承诺库的情况下,处理这个问题的推荐模式是什么?


当前回答

这是我的custom settdpromiseall ()

const settledPromiseAll = function(promisesArray) {
  var savedError;

  const saveFirstError = function(error) {
    if (!savedError) savedError = error;
  };
  const handleErrors = function(value) {
    return Promise.resolve(value).catch(saveFirstError);
  };
  const allSettled = Promise.all(promisesArray.map(handleErrors));

  return allSettled.then(function(resolvedPromises) {
    if (savedError) throw savedError;
    return resolvedPromises;
  });
};

与Promise.all相比

如果所有的承诺都得到了解决,那么它的性能与标准承诺完全相同。 如果多个promise中有一个被拒绝,它返回第一个被拒绝的promise,与标准的promise大致相同,但不同的是,它等待所有的promise都被解决/拒绝。

为了勇敢,我们可以改变。

(function() {
  var stdAll = Promise.all;

  Promise.all = function(values, wait) {
    if(!wait)
      return stdAll.call(Promise, values);

    return settledPromiseAll(values);
  }
})();

小心。一般来说,我们从不改变内置程序,因为这可能会破坏其他不相关的JS库,或者与未来对JS标准的更改发生冲突。

我的承诺是向后兼容的承诺。并扩展其功能。

开发标准的人——为什么不将其包含到新的Promise标准中呢?

其他回答

我认为下面提供了一个稍微不同的方法……比较fn_fast_fail()和fn_slow_fail()…尽管后者并没有因此而失败……你可以检查a和b中是否有一个或两个都是Error实例,如果你想让它到达catch块,就抛出这个错误(例如if (b instanceof Error) {throw b;})。参见jsfiddle。

var p1 = new Promise((resolve, reject) => { 
    setTimeout(() => resolve('p1_delayed_resolvement'), 2000); 
}); 

var p2 = new Promise((resolve, reject) => {
    reject(new Error('p2_immediate_rejection'));
});

var fn_fast_fail = async function () {
    try {
        var [a, b] = await Promise.all([p1, p2]);
        console.log(a); // "p1_delayed_resolvement"
        console.log(b); // "Error: p2_immediate_rejection"
    } catch (err) {
        console.log('ERROR:', err);
    }
}

var fn_slow_fail = async function () {
    try {
        var [a, b] = await Promise.all([
            p1.catch(error => { return error }),
            p2.catch(error => { return error })
        ]);
        console.log(a); // "p1_delayed_resolvement"
        console.log(b); // "Error: p2_immediate_rejection"
    } catch (err) {
        // we don't reach here unless you throw the error from the `try` block
        console.log('ERROR:', err);
    }
}

fn_fast_fail(); // fails immediately
fn_slow_fail(); // waits for delayed promise to resolve

这应该与Q的做法一致:

if(!Promise.allSettled) {
    Promise.allSettled = function (promises) {
        return Promise.all(promises.map(p => Promise.resolve(p).then(v => ({
            state: 'fulfilled',
            value: v,
        }), r => ({
            state: 'rejected',
            reason: r,
        }))));
    };
}

更新,你可能需要使用内置的本机promise . allsettle:

Promise.allSettled([promise]).then(([result]) => {
   //reach here regardless
   // {status: "fulfilled", value: 33}
});

作为一个有趣的事实,下面的答案是将该方法添加到语言中的现有技术:]


当然,你只需要反思一下:

const reflect = p => p.then(v => ({v, status: "fulfilled" }),
                            e => ({e, status: "rejected" }));

reflect(promise).then((v) => {
    console.log(v.status);
});

或者使用ES5:

function reflect(promise){
    return promise.then(function(v){ return {v:v, status: "fulfilled" }},
                        function(e){ return {e:e, status: "rejected" }});
}


reflect(promise).then(function(v){
    console.log(v.status);
});

或者在你的例子中:

var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr.map(reflect)).then(function(results){
    var success = results.filter(x => x.status === "fulfilled");
});

本杰明的回答为解决这个问题提供了一个很好的抽象,但我希望有一个不那么抽象的解决方案。解决这个问题的显式方法是简单地对内部promise调用.catch,并从它们的回调返回错误。

let a = new Promise((res, rej) => res('Resolved!')),
    b = new Promise((res, rej) => rej('Rejected!')),
    c = a.catch(e => { console.log('"a" failed.'); return e; }),
    d = b.catch(e => { console.log('"b" failed.'); return e; });

Promise.all([c, d])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));

Promise.all([a.catch(e => e), b.catch(e => e)])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));

更进一步,你可以写一个通用的catch处理程序,看起来像这样:

const catchHandler = error => ({ payload: error, resolved: false });

然后你就可以

> Promise.all([a, b].map(promise => promise.catch(catchHandler))
    .then(results => console.log(results))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!',  { payload: Promise, resolved: false } ]

这样做的问题是,被捕获的值将与未被捕获的值有不同的接口,所以要清理这个,你可以这样做:

const successHandler = result => ({ payload: result, resolved: true });

现在你可以这样做:

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]

为了保持干燥,你看到了本杰明的答案:

const reflect = promise => promise
  .then(successHandler)
  .catch(catchHander)

现在是什么样子

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]

第二种解决方案的优点是它是抽象的和DRY的。缺点是你有更多的代码,你必须记住反映你所有的承诺,使事情一致。

我将把我的解决方案描述为explicit和KISS,但确实不那么健壮。接口不能保证您确切地知道承诺是成功了还是失败了。

例如,你可以这样写:

const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));

这个不会被。catch抓到,所以

> Promise.all([a, b].map(promise => promise.catch(e => e))
    .then(results => console.log(results))
< [ Error, Error ]

没有办法判断哪个是致命的,哪个不是。如果这很重要,那么您将希望强制和接口跟踪它是否成功(这是reflect所做的)。

如果你只是想优雅地处理错误,那么你可以把错误视为未定义的值:

> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
    .then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]

在我的例子中,我不需要知道错误或它是如何失败的——我只关心是否有值。我将让生成承诺的函数负责记录特定的错误。

const apiMethod = () => fetch()
  .catch(error => {
    console.log(error.message);
    throw error;
  });

这样,应用程序的其余部分就可以忽略它的错误,并将其视为一个未定义的值。

我希望我的高级函数安全失败,而不担心它的依赖项失败的细节,当我必须做出权衡时,我也更喜欢KISS而不是DRY——这就是我最终选择不使用reflect的原因。

这是我的custom settdpromiseall ()

const settledPromiseAll = function(promisesArray) {
  var savedError;

  const saveFirstError = function(error) {
    if (!savedError) savedError = error;
  };
  const handleErrors = function(value) {
    return Promise.resolve(value).catch(saveFirstError);
  };
  const allSettled = Promise.all(promisesArray.map(handleErrors));

  return allSettled.then(function(resolvedPromises) {
    if (savedError) throw savedError;
    return resolvedPromises;
  });
};

与Promise.all相比

如果所有的承诺都得到了解决,那么它的性能与标准承诺完全相同。 如果多个promise中有一个被拒绝,它返回第一个被拒绝的promise,与标准的promise大致相同,但不同的是,它等待所有的promise都被解决/拒绝。

为了勇敢,我们可以改变。

(function() {
  var stdAll = Promise.all;

  Promise.all = function(values, wait) {
    if(!wait)
      return stdAll.call(Promise, values);

    return settledPromiseAll(values);
  }
})();

小心。一般来说,我们从不改变内置程序,因为这可能会破坏其他不相关的JS库,或者与未来对JS标准的更改发生冲突。

我的承诺是向后兼容的承诺。并扩展其功能。

开发标准的人——为什么不将其包含到新的Promise标准中呢?