处理 Promise.all 中的错误

Handling errors in Promise.all

我有一组 Promise,我正在用 Promise.all(arrayOfPromises);

解决

我继续承诺链。看起来像这样

existingPromiseChain = existingPromiseChain.then(function() {
  var arrayOfPromises = state.routes.map(function(route){
    return route.handler.promiseHandler();
  });
  return Promise.all(arrayOfPromises)
});

existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
  // do stuff with my array of resolved promises, eventually ending with a res.send();
});

我想添加一个 catch 语句来处理单个 promise,以防它出错,但是当我尝试时,Promise.all returns 它找到的第一个错误(忽略其余的),然后我无法从数组中的其余承诺中获取数据(没有错误)。

我试过做类似的事情..

existingPromiseChain = existingPromiseChain.then(function() {
      var arrayOfPromises = state.routes.map(function(route){
        return route.handler.promiseHandler()
          .then(function(data) {
             return data;
          })
          .catch(function(err) {
             return err
          });
      });
      return Promise.all(arrayOfPromises)
    });

existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
      // do stuff with my array of resolved promises, eventually ending with a res.send();
});

但这并没有解决。

谢谢!

--

编辑:

下面的回答完全正确,代码由于其他原因被破坏了。如果有人感兴趣,这就是我最终得到的解决方案 ...

Node Express 服务器链

serverSidePromiseChain
    .then(function(AppRouter) {
        var arrayOfPromises = state.routes.map(function(route) {
            return route.async();
        });
        Promise.all(arrayOfPromises)
            .catch(function(err) {
                // log that I have an error, return the entire array;
                console.log('A promise failed to resolve', err);
                return arrayOfPromises;
            })
            .then(function(arrayOfPromises) {
                // full array of resolved promises;
            })
    };

API呼唤(route.async呼唤)

return async()
    .then(function(result) {
        // dispatch a success
        return result;
    })
    .catch(function(err) {
        // dispatch a failure and throw error
        throw err;
    });

Promise.all.catch 放在 .then 之前似乎是为了捕获原始承诺中的任何错误,然后将整个数组返回到下一个 .then

谢谢!

Promise.all 是全有或全无。一旦数组中的所有承诺都解决,它就会解决,或者一旦其中一个拒绝就拒绝。换句话说,它要么以所有已解析值的数组解析,要么以单个错误拒绝。

一些库有一个叫做 Promise.when 的东西,我知道它会等待数组中的 all 承诺来解决或拒绝,但我不熟悉有了它,它不在 ES6 中。

您的代码

我同意其他人的意见,您的修复应该有效。它应该使用一个可能包含成功值和错误对象混合的数组来解析。在成功路径中传递错误对象是不常见的,但假设您的代码需要它们,我认为这没有问题。

我能想到它为什么会 "not resolve" 的唯一原因是它在你没有向我们展示的代码中失败,而你没有看到任何关于此的错误消息的原因是因为这个承诺链不会以最终捕获结束(就您向我们展示的内容而言)。

我冒昧地从你的例子中提取了 "existing chain" 并用一个 catch 终止了链。这可能不适合你,但对于阅读本文的人来说,重要的是始终 return 或终止链,否则潜在的错误,甚至是编码错误,都会被隐藏(我怀疑这里发生了什么):

Promise.all(state.routes.map(function(route) {
  return route.handler.promiseHandler().catch(function(err) {
    return err;
  });
}))
.then(function(arrayOfValuesOrErrors) {
  // handling of my array containing values and/or errors. 
})
.catch(function(err) {
  console.log(err.message); // some coding error in handling happened
});

我写了一个npm库来处理这个问题比较漂亮。 https://github.com/wenshin/promiseallend

安装

npm i --save promiseallend

2017-02-25新增api,不食言原则

const promiseAllEnd = require('promiseallend');

const promises = [Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)];
const promisesObj = {k1: Promise.resolve(1), k2: Promise.reject('error'), k3: Promise.resolve(2)};

// input promises with array
promiseAllEnd(promises, {
    unhandledRejection(error, index) {
        // error is the original error which is 'error'.
        // index is the index of array, it's a number.
        console.log(error, index);
    }
})
    // will call, data is `[1, undefined, 2]`
    .then(data => console.log(data))
    // won't call
    .catch(error => console.log(error.detail))

// input promises with object
promiseAllEnd(promisesObj, {
    unhandledRejection(error, prop) {
        // error is the original error.
        // key is the property of object.
        console.log(error, prop);
    }
})
    // will call, data is `{k1: 1, k3: 2}`
    .then(data => console.log(data))
    // won't call
    .catch(error => console.log(error.detail))

// the same to `Promise.all`
promiseAllEnd(promises, {requireConfig: true})
    // will call, `error.detail` is 'error', `error.key` is number 1.
    .catch(error => console.log(error.detail))

// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [false, true, false]})
    // won't call
    .then(data => console.log(data))
    // will call, `error.detail` is 'error', `error.key` is number 1.
    .catch(error => console.log(error.detail))

// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [true, false, false]})
    // will call, data is `[1, undefined, 2]`.
    .then(data => console.log(data))
    // won't call
    .catch(error => console.log(error.detail))

————————————————————————————————

老坏了api,不要用了!

let promiseAllEnd = require('promiseallend');

// input promises with array
promiseAllEnd([Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)])
    .then(data => console.log(data)) // [1, undefined, 2]
    .catch(error => console.log(error.errorsByKey)) // {1: 'error'}

// input promises with object
promiseAllEnd({k1: Promise.resolve(1), k2: Promise.reject('error'), k3: Promise.resolve(2)})
    .then(data => console.log(data)) // {k1: 1, k3: 2}
    .catch(error => console.log(error.errorsByKey)) // {k2: 'error'}

如果你能使用 q 库 https://github.com/kriskowal/q 它有 q.allSettled() 方法可以解决这个问题 您可以根据已完成或已拒绝的状态处理每个承诺 所以

existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
  return route.handler.promiseHandler();
});
return q.allSettled(arrayOfPromises)
});

existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
//so here you have all your promises the fulfilled and the rejected ones
// you can check the state of each promise
arrayResolved.forEach(function(item){
   if(item.state === 'fulfilled'){ // 'rejected' for rejected promises
     //do somthing
   } else {
     // do something else
   }
})
// do stuff with my array of resolved promises, eventually ending with a res.send();
});

为了继续 Promise.all 循环(即使 Promise 拒绝),我编写了一个名为 executeAllPromises 的实用函数。此效用函数 returns 具有 resultserrors 的对象。

我们的想法是,您传递给 executeAllPromises 的所有 Promise 都将被包装到一个新的 Promise 中,该 Promise 将始终解析。新的 Promise 解析为一个有 2 个点的数组。第一个位置保存解析值(如果有),第二个位置保存错误(如果包装的 Promise 拒绝)。

作为最后一步,executeAllPromises 累积包装承诺的所有值和 returns 具有 results 数组和 errors 数组的最终对象。

代码如下:

function executeAllPromises(promises) {
  // Wrap all Promises in a Promise that will always "resolve"
  var resolvingPromises = promises.map(function(promise) {
    return new Promise(function(resolve) {
      var payload = new Array(2);
      promise.then(function(result) {
          payload[0] = result;
        })
        .catch(function(error) {
          payload[1] = error;
        })
        .then(function() {
          /* 
           * The wrapped Promise returns an array:
           * The first position in the array holds the result (if any)
           * The second position in the array holds the error (if any)
           */
          resolve(payload);
        });
    });
  });

  var errors = [];
  var results = [];

  // Execute all wrapped Promises
  return Promise.all(resolvingPromises)
    .then(function(items) {
      items.forEach(function(payload) {
        if (payload[1]) {
          errors.push(payload[1]);
        } else {
          results.push(payload[0]);
        }
      });

      return {
        errors: errors,
        results: results
      };
    });
}

var myPromises = [
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.reject(new Error('3')),
  Promise.resolve(4),
  Promise.reject(new Error('5'))
];

executeAllPromises(myPromises).then(function(items) {
  // Result
  var errors = items.errors.map(function(error) {
    return error.message
  }).join(',');
  var results = items.results.join(',');
  
  console.log(`Executed all ${myPromises.length} Promises:`);
  console.log(`— ${items.results.length} Promises were successful: ${results}`);
  console.log(`— ${items.errors.length} Promises failed: ${errors}`);
});

新答案

const results = await Promise.all(promises.map(p => p.catch(e => e)));
const validResults = results.filter(result => !(result instanceof Error));

未来的承诺API

这就是 Promise.all 设计的工作原理。如果单个 promise reject()'s,整个方法立即失败。

有些用例可能希望 Promise.all 允许 promise 失败。要做到这一点,只需不要在您的承诺中使用任何 reject() 语句。但是,为了确保您的 app/script 不会在任何单个基础承诺 never 得到响应的情况下冻结,您需要对其设置超时。

function getThing(uid,branch){
    return new Promise(function (resolve, reject) {
        xhr.get().then(function(res) {
            if (res) {
                resolve(res);
            } 
            else {
                resolve(null);
            }
            setTimeout(function(){reject('timeout')},10000)
        }).catch(function(error) {
            resolve(null);
        });
    });
}

我们可以在单个承诺级别处理拒绝,因此当我们在结果数组中获取结果时,被拒绝的数组索引将是 undefined。我们可以根据需要处理这种情况,并使用剩余的结果。

这里我拒绝了第一个promise,所以它是未定义的,但是我们可以使用第二个promise的结果,它在索引1处。

const manyPromises = Promise.all([func1(), func2()]).then(result => {
    console.log(result[0]);  // undefined
    console.log(result[1]);  // func2
});

function func1() {
    return new Promise( (res, rej) => rej('func1')).catch(err => {
        console.log('error handled', err);
    });
}

function func2() {
    return new Promise( (res, rej) => setTimeout(() => res('func2'), 500) );
}

对于那些在这里绊倒的使用 ES8 的人,您可以使用 async functions:

执行以下操作
var arrayOfPromises = state.routes.map(async function(route){
  try {
    return await route.handler.promiseHandler();
  } catch(e) {
    // Do something to handle the error.
    // Errored promises will return whatever you return here (undefined if you don't return anything).
  }
});

var resolvedPromises = await Promise.all(arrayOfPromises);

你考虑过Promise.prototype.finally()吗?

它似乎被设计为完全按照您的意愿行事 - 一旦所有承诺都已解决 (resolved/rejected) 就执行一个函数,而不管某些承诺是否被拒绝。

来自MDN documentation

如果您想在 promise 结算后进行一些处理或清理,而不管其结果如何,finally() 方法会很有用。

finally() 方法与调用 .then(onFinally, onFinally) 非常相似,但有几点不同:

创建内联函数时,您可以传递一次,而不是被迫声明两次或为其创建一个变量。

finally 回调不会收到任何参数,因为没有可靠的方法来确定承诺是已实现还是已被拒绝。这个用例正是当你不关心拒绝原因或履行价值时,所以没有必要提供它。

不像Promise.resolve(2).then(() => {}, () => {})(会被解析为undefined),Promise.resolve(2).finally(() => {})会被解析为2。 类似地,与 Promise.reject(3).then(() => {}, () => {})(将由 undefined 满足)不同,Promise.reject(3).finally(() => {}) 将被 3.

拒绝

== 回退 ==

如果您的 JavaScript 版本不支持 Promise.prototype.finally(),您可以使用 Jake Archibald 中的解决方法:Promise.all(promises.map(p => p.catch(() => undefined)));

正如@jib 所说,

Promise.all is all or nothing.

不过,您可以控制某些 "allowed" 失败的承诺,我们希望继续 .then

例如

  Promise.all([
    doMustAsyncTask1,
    doMustAsyncTask2,
    doOptionalAsyncTask
    .catch(err => {
      if( /* err non-critical */) {
        return
      }
      // if critical then fail
      throw err
    })
  ])
  .then(([ mustRes1, mustRes2, optionalRes ]) => {
    // proceed to work with results
  })

使用异步等待 -

这里一个异步函数 func1 return 解析值,而 func2 抛出一个错误并且 return 在这种情况下 return 一个 null,我们可以按我们想要的方式处理它 return相应地。

const callingFunction  = async () => {
    const manyPromises = await Promise.all([func1(), func2()]);
    console.log(manyPromises);
}


const func1 = async () => {
    return 'func1'
}

const func2 = async () => {
    try {
        let x;
        if (!x) throw "x value not present"
    } catch(err) {
       return null
    }
}

callingFunction();

输出为 - [ 'func1', 空 ]

或者,如果您遇到一种情况,当出现一次失败时您并不特别关心已解决的承诺的价值,但您仍然希望它们具有 运行,您可以这样做当它们都成功时,它将像往常一样解决承诺,并在其中任何一个失败时拒绝失败的承诺:

function promiseNoReallyAll (promises) {
  return new Promise(
    async (resolve, reject) => {
      const failedPromises = []

      const successfulPromises = await Promise.all(
        promises.map(
          promise => promise.catch(error => {
            failedPromises.push(error)
          })
        )
      )

      if (failedPromises.length) {
        reject(failedPromises)
      } else {
        resolve(successfulPromises)
      }
    }
  )
}

您始终可以以捕获失败并返回商定值(例如 error.message)的方式包装您的 promise 返回函数,因此异常不会一直滚动到 Promise.all 功能并禁用它。

async function resetCache(ip) {

    try {

        const response = await axios.get(`http://${ip}/resetcache`);
        return response;

    }catch (e) {

        return {status: 'failure', reason: 'e.message'};
    }

}

我找到了一种方法(解决方法)来执行此操作而不使其同步。

所以正如之前提到的 Promise.all 是 none 的全部。

所以...使用封闭的承诺来捕获并强制解析。


      let safePromises = originalPrmises.map((imageObject) => {
            return new Promise((resolve) => {
              // Do something error friendly
              promise.then(_res => resolve(res)).catch(_err => resolve(err))
            })
        })
    })

    // safe
    return Promise.all(safePromises)

您需要知道如何识别结果中的错误。如果您没有标准的预期错误,我建议您 运行 对 catch 块中的每个错误进行转换,使其在结果中可识别。

try {
  let resArray = await Promise.all(
    state.routes.map(route => route.handler.promiseHandler().catch(e => e))
  );

  // in catch(e => e) you can transform your error to a type or object
  // that makes it easier for you to identify whats an error in resArray
  // e.g. if you expect your err objects to have e.type, you can filter
  // all errors in the array eg
  // let errResponse = resArray.filter(d => d && d.type === '<expected type>')
  // let notNullResponse = resArray.filter(d => d)

  } catch (err) {
    // code related errors
  }

这不是错误日志的最佳方式,但您始终可以将所有内容设置为 promiseAll 的数组,并将结果存储到新变量中。

如果您使用 graphQL,则无论如何都需要对响应进行后处理,如果它找不到正确的引用,它会使应用程序崩溃,从而缩小问题所在的范围

const results = await Promise.all([
  this.props.client.query({
    query: GET_SPECIAL_DATES,
  }),
  this.props.client.query({
    query: GET_SPECIAL_DATE_TYPES,
  }),
  this.props.client.query({
    query: GET_ORDER_DATES,
  }),
]).catch(e=>console.log(e,"error"));
const specialDates = results[0].data.specialDates;
const specialDateTypes = results[1].data.specialDateTypes;
const orderDates = results[2].data.orders;

ES2020Promise 类型引入了新方法:Promise.allSettled().

Promise.allSettled gives you a signal when all the input promises are settled, which means they’re either fulfilled or rejected. This is useful in cases where you don’t care about the state of the promise, you just want to know when the work is done, regardless of whether it was successful.

(async function() {
  const promises = [
    fetch('//api.stackexchange.com/2.2'), // succeeds
    fetch('/this-will-fail') // fails
  ];

  const result = await Promise.allSettled(promises);
  console.log(result.map(promise => promise.status));
  // ['fulfilled', 'rejected']
})();

v8 blog post 阅读更多内容。

Promise.allSettled

而不是 Promise.all 使用 Promise.allSettled 等待所有承诺结算,不管结果如何

let p1 = new Promise(resolve => resolve("result1"));
let p2 = new Promise( (resolve,reject) => reject('some troubles') );
let p3 = new Promise(resolve => resolve("result3"));

// It returns info about each promise status and value
Promise.allSettled([p1,p2,p3]).then(result=> console.log(result));

填充

if (!Promise.allSettled) {
  const rejectHandler = reason => ({ status: 'rejected', reason });
  const resolveHandler = value => ({ status: 'fulfilled', value });

  Promise.allSettled = function (promises) {
    const convertedPromises = promises
      .map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
    return Promise.all(convertedPromises);
  };
}

Promise.allSettled 带过滤器

const promises = [
  fetch('/api-call-1'),
  fetch('/api-call-2'),
  fetch('/api-call-3'),
];
// Imagine some of these requests fail, and some succeed.

const resultFilter = (result, error) => result.filter(i => i.status === (!error ? 'fulfilled' : 'rejected')).map(i => (!error ? i.value : i.reason));

const result = await Promise.allSettled(promises);

const fulfilled = resultFilter(result); // all fulfilled results
const rejected = resultFilter(result, true); // all rejected results
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
let sum = 0;
let promiseErrorArr = [];

Promise.allSettled(promises)
.then((results) => {
          results.forEach(result => {
            if (result.status === "rejected") {
              sum += 1;
              promiseErrorArr.push(result)
            }
          })
    return ( (sum>0) ? promiseFailed() : promisePassed())
})

function promiseFailed(){
  console.log('one or all failed!')
  console.log(promiseErrorArr)
}

function promisePassed(){
  console.log('all passed!')
}

// expected output:
// "one or all failed!"
// Array [Object { status: "rejected", reason: "foo" }]