繁体   English   中英

处理 Promise.all 中的错误

[英]Handling errors in Promise.all

我有一系列 Promises,我正在使用Promise.all(arrayOfPromises);

我go继续promise链。 看起来像这样

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返回它找到的第一个错误(忽略其余部分),然后我无法从 rest 获取数据数组中的承诺(没有错误)。

我试过做类似的事情..

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

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

谢谢!

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

一些库有一个叫做Promise.when东西,我理解它会等待数组中的所有承诺要么解决要么拒绝,但我不熟悉它,它不在 ES6 中。

你的代码

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

我能想到为什么它“无法解决”的唯一原因是它在您没有向我们展示的代码中失败,并且您没有看到任何关于此的错误消息的原因是因为此承诺链没有以最终抓住(就您向我们展示的内容而言)。

我冒昧地从您的示例中分解出“现有链”并用捕获终止链。 这可能不适合您,但对于阅读本文的人来说,始终返回或终止链很重要,否则潜在的错误,甚至编码错误,将被隐藏(我怀疑这里发生了这种情况):

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

新答案

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

未来承诺API

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

Promise.allSettled会在所有输入承诺都解决时给你一个信号,这意味着它们要么被履行,要么被拒绝。 这在您不关心 Promise 的状态,只想知道工作何时完成的情况下很有用,无论它是否成功。

 (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 博客文章中阅读更多内容。

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

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

作为最后一步, executeAllPromises累积包装的承诺的所有值并返回最终对象,其中包含一个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}`); });

正如@jib 所说,

Promise.all全有要么全无。

但是,您可以控制某些“允许”失败的承诺,我们希望继续.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
  })

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

使用异步等待 -

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

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', null ]

如果你开始使用 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();
});

对于那些在这里绊倒的使用 ES8 的人,您可以使用异步函数执行以下操作:

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

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

这里我拒绝了第一个承诺,所以它是未定义的,但我们可以使用第二个承诺的结果,它在索引 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) ); }

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

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

MDN 文档

如果您想在承诺完成后进行一些处理或清理,无论其结果如何, finally()方法都非常有用。

finally()方法与调用.then(onFinally, onFinally)非常相似.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)));

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" }]

或者,如果您有一种情况,当发生一次失败时您并不特别关心已解决的承诺的值,但您仍然希望它们运行,您可以执行类似这样的操作,这将在正常情况下解决承诺当它们中的任何一个失败时,它们都会成功并拒绝失败的承诺:

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.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完全没有。

所以......使用一个封闭的承诺来捕捉并强制解决。


      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;

不幸的是,我没有足够的声誉来发表评论(或者做很多事情,真的),所以我将此作为对埃里克在这里的回答的回应发布。

执行器 function 也可以是异步 function。但是,这通常是一个错误,原因如下:

  • 如果异步执行器 function 抛出错误,错误将丢失,不会导致新构造的 Promise 拒绝。 这可能会使调试和处理某些错误变得困难。
  • 如果一个 Promise 执行器 function 正在使用 await,这通常是一个标志,表明实际上没有必要使用新的 Promise 构造函数,或者可以减少新的 Promise 构造函数的 scope。

从这个解释为什么Promises 不应该使用异步执行器 function

相反,您应该选择Promise.allSettled() ,正如 Asaf 在此处所建议的那样

allSettled的帮助下,我们现在可以读取每个 promise 的状态,并单独处理每个错误,而不会丢失任何关键信息

const promises = [
    fetch('/api/first'), // first
    fetch('/api/second') // second
];

最简单的方法是处理错误

const [firstResult, secondResult] = await Promise.allSettled(promises)


 // Process first
 if (firstResult.status === 'rejected') {
   const err = firstResult.reason
   // Here you can handle error
 } else {
   const first = firstResult.value
 }

  // Process second
 if (secondResult.status === 'rejected') {
   const err = secondResult.reason
   // Here you can handle error
 } else {
   const second = secondResult.value
 }

处理错误的好方法

const results = await Promise.allSettled(promises);
const [first, second] = handleResults(results)


function handleResults(results) {
  const errors = results.filter(result => result.status === 'rejected').map(result => result.reason)

  if (errors.length) {
    // Aggregate all errors into one
    throw new AggregateError(errors)
  }

  return results.map(result => result.value)
}

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

在某些用例中,人们可能希望Promise.all允许承诺失败。 要实现这一点,请不要在您的承诺中使用任何reject()语句。 但是,为了确保您的应用程序/脚本不会在任何单个底层承诺从未得到响应的情况下冻结,您需要对其设置超时。

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

我写了一个 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'}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM