假设我有一组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   

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

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


当前回答

我会这样做:

var err = [fetch('index.html').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); }),
fetch('http://does-not-exist').then((success) => { return Promise.resolve(success); }).catch((e) => { return Promise.resolve(e); })];

Promise.all(err)
.then(function (res) { console.log('success', res) })
.catch(function (err) { console.log('error', err) }) //never 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

有一个完成的提议,一个函数,可以完成这个本机,在香草Javascript:承诺。allsettle已经进入了第4阶段,在ES2020中正式发布,并在所有现代环境中实现。它和另一个答案中的反射函数很相似。这里有一个例子,来自提案页面。以前,你必须这样做:

function reflect(promise) {
  return promise.then(
    (v) => {
      return { status: 'fulfilled', value: v };
    },
    (error) => {
      return { status: 'rejected', reason: error };
    }
  );
}

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.all(promises.map(reflect));
const successfulPromises = results.filter(p => p.status === 'fulfilled');

使用的承诺。取而代之的是,上述内容将等价于:

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
const successfulPromises = results.filter(p => p.status === 'fulfilled');

那些使用现代环境的人不需要任何库就可以使用这种方法。在这些情况下,下面的代码段应该可以正常运行:

Promise.allSettled ([ Promise.resolve (' a '), Promise.reject (b) ]) 不要犹豫(console.log);

输出:

[
  {
    "status": "fulfilled",
    "value": "a"
  },
  {
    "status": "rejected",
    "reason": "b"
  }
]

对于较老的浏览器,这里有一个符合规范的填充。

我真的很喜欢本杰明的回答,以及他如何把所有的承诺都变成了总是能解决但有时会导致错误的承诺。:) 这是我对你的要求的尝试,以防你正在寻找替代方案。该方法简单地将错误视为有效的结果,其编码类似于Promise。否则:

Promise.settle = function(promises) {
  var results = [];
  var done = promises.length;

  return new Promise(function(resolve) {
    function tryResolve(i, v) {
      results[i] = v;
      done = done - 1;
      if (done == 0)
        resolve(results);
    }

    for (var i=0; i<promises.length; i++)
      promises[i].then(tryResolve.bind(null, i), tryResolve.bind(null, i));
    if (done == 0)
      resolve(results);
  });
}

Benjamin Gruenbaum的回答当然很好。但我也能看出内森哈根的观点在抽象层面上显得模糊。拥有像e和v这样的短对象属性也没有帮助,但当然这是可以改变的。

在Javascript中有一个标准的Error对象,称为Error。理想情况下,您总是抛出该实例/后代。这样做的好处是您可以使用instanceof Error,并且您知道某些东西是错误的。

利用这个想法,下面是我对这个问题的看法。

基本上捕捉错误,如果错误不是error类型,则将错误包装在error对象中。结果数组将具有已解析的值或可以检查的Error对象。

catch内部的instanceof,是为了防止你使用一些外部库可能会reject("error"),而不是reject(new error ("error"))。

当然,如果您解决了一个错误,您也可以有承诺,但在这种情况下,无论如何都应该将其视为错误,就像最后一个示例所示的那样。

这样做的另一个好处是,数组析构保持简单。

const [value1, value2] = PromiseAllCatch(promises);
if (!(value1 instanceof Error)) console.log(value1);

而不是

const [{v: value1, e: error1}, {v: value2, e: error2}] = Promise.all(reflect..
if (!error1) { console.log(value1); }

你可能会说!error1检查比instanceof简单,但你也必须销毁v和e。

function PromiseAllCatch(promises) { return Promise.all(promises.map(async m => { try { return await m; } catch(e) { if (e instanceof Error) return e; return new Error(e); } })); } async function test() { const ret = await PromiseAllCatch([ (async () => "this is fine")(), (async () => {throw new Error("oops")})(), (async () => "this is ok")(), (async () => {throw "Still an error";})(), (async () => new Error("resolved Error"))(), ]); console.log(ret); console.log(ret.map(r => r instanceof Error ? "error" : "ok" ).join(" : ")); } test();