处理Promise.all中的错误


265

我有一系列要解决的承诺 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返回它发现的第一个错误(忽略其余的),然后我无法从其他promise中获取数据数组(没有错误)。

我尝试做类似..

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

但这并不能解决。

谢谢!

--

编辑:

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

节点快速服务器链

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用于Promise.all在之前.then似乎已经担任了从原来的承诺捕捉任何错误,但随后整个数组返回到下一个目的.then

谢谢!


2
您的尝试似乎应该可行……也许以后某个地方出现另一个问题?
Ry-

.then(function(data) { return data; })可以完全省略
Bergi

上述问题无法解决的唯一原因是,如果您没有向我们展示thencatch处理程序中的所有代码,并且内部抛出错误。顺便问一下,这个节点吗?

1
您在“现有链”中没有最后的收获,因此可能会出现一些您没有看到的错误,这些错误可能解释了为什么“无法解决”。尝试添加该代码,看看会遇到什么错误。
2015年

Answers:


189

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

有些库中有一个叫做的东西Promise.when,据我所知,它会等待数组中的所有 promise解析或拒绝,但是我对此并不熟悉,ES6中也没有。

您的密码

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

我能想到它“无法解决”的唯一原因是,它在代码中失败,您没有向我们显示;而您没有看到有关此消息的任何错误消息,是因为该诺言链没有以final结尾捕捉(就您向我们展示的内容而言)。

我已自由地从您的示例中排除了“现有链”,并以一条链来终止该链。这可能不适合您,但是对于阅读此书的人来说,始终返回或终止链很重要,否则潜在的错误(甚至编码错误)将被隐藏(这是我怀疑在这里发生的事情):

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

4
您(和以上评论)是正确的。我的route.handler.promiseHandler需要.catch()并返回错误。我还需要在链的末尾添加最终的.catch()。感谢您传达在链的每一步都有成功/错误处理程序的重要性:)。
2015年

2
我还发现,如果我在.catch()中为route.handler.promiseHandler抛出错误,它将自动进入最终的捕获。如果返回错误,它将执行我想要的操作并处理整个数组。
2015年

2
现在有一种标准的方法Promise.allSettled(),可以得到不错的支持。请参阅参考
安德烈Maugars

是的,Promise.all当第一个线程失败时失败。但是不幸的是,所有其他线程仍然继续运行,直到完成。没有任何事情可以取消,甚至更糟:无法取消中的线程Promise。因此,无论线程在做什么(和操作),它们都将继续执行,更改状态和变量,使用CPU,但最后它们不会返回结果。您需要意识到这一点,以免产生混乱,例如,当您重复/重试呼叫时。
马克·瓦克林

143

新答案

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

未来承诺API


11
虽然e不必一定要Error。例如,如果有人像这样返回它,它可能是一个字符串Promise.reject('Service not available')
Klesun

@ArturKlesun然后我们如何分类哪个承诺导致错误而哪个没有导致错误呢?
Shubham Jain

5
@ shubham-jain与.then().catch()Promise.resolve()将价值传递给前者,而Promise.reject()将价值传递给后者。您可以将它们包装在object中,例如:p.then(v => ({success: true, value: v})).catch(e => ({success: false, error: e}))
克莱森

2
为什么要过滤结果?如果要对结果做任何事情,那就没有意义了–您需要命令知道哪个返回值来自哪个promise!
瑞安·泰勒

21

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

这个想法是,您传递给所有的executeAllPromisesPromise都会包装到一个新的Promise中,它将永远解决。新的Promise分解为具有2个点的阵列。第一个点保留解析值(如果有),第二个点保留错误(如果包装的Promise拒绝)。

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


2
这可以更简单地完成。见stackoverflow.com/a/36115549/918910
三角帆

18

ES2020引入了Promise类型的新方法:Promise.allSettled()
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 result = await Promise.allSettled(promises);
console.log(result.map(x=>s.status));
// ['fulfilled', 'fulfilled', 'rejected']

在v8博客文章https://v8.dev/features/promise-combinators中阅读更多内容


13

正如@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
  })

6

如果您可以使用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();
});

由于您建议使用某些库(q),因此如果您提供与该问题相关的用法示例,它将更加有用。就目前而言,您的答案并未说明该库如何帮助解决问题。
ishmaelMakitla '16

根据建议添加了一个示例
Mohamed Mahmoud

1
大约在2018年,您应该始终看到Sindre提供的功能:-)。 github.com/sindresorhus/p-settle。使用Sindre的单一用途模块,您不必只花一点时间就可以导入像q这样的庞大库。
DKebler '18

6

使用异步等待-

在这种情况下,一个异步函数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]


4

对于使用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);

3

我们可以在单个promise级别处理拒绝,因此当我们在结果数组中获取结果时,已被拒绝的数组索引将为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) );
}


如果我们使用异步等待,您如何做类似的事情?
Rudresh Ajgaonkar '19

我已经回答了您的问题,请找到答案的链接。stackoverflow.com/a/55216763/4079716
Nayan Patel

2

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

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

MDN文档中

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


1
是的,直到Promises.allSettled()实际实现(在此由MDN记录),然后Promises.all.finally()似乎可以完成相同的事情。我要尝试一下...
jamess

@jamess为什么不将此评论作为正确的答案?答案均未提及ES6 allSettled()
pravin

@pravin-据我所知,allSettled()尚未在任何地方实现,所以我不想超越现实。我在上取得了成功Promises.all(myPromiseArray).finally(),而这个答案很合适。一旦allSettled()实际存在,那么我可能会对其进行测试并找出其实际工作方式。在那之前,谁知道浏览器将实际实现什么?除非您有相反的最新信息……
jamess

@jamess是的,它仍然处于起草阶段。.但是最新的FF和chrome似乎完全支持它。.不确定它的稳定性吗.. Mozilla Docs无论如何,我要提出的观点是,它将更容易找到如果这是一个答案而不是评论..虽然您打电话:)
pravin

@pravin-在我发表评论时,它没有在任何地方实现。我刚刚在Firefox和Chrome中进行了测试:Firefox Promise.allSettled中未实现,但Chrome中确实存在。仅仅因为文档说它已实现,并不意味着它确实已实现。我不会在任何时候使用它。
jamess

0

或者,如果您遇到的情况是当一个失败时您并不特别在意已解决的承诺的值,但您仍然希望它们能够继续运行,那么您可以执行类似的操作,以在正常情况下以承诺解决问题他们全部成功,当其中任何一个失败时,都将失败的承诺拒之门外:

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

0

您始终可以将诺言返回函数包装为能够捕获故障并返回约定值(例如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'};
    }

}

0

我找到了一种方法(解决方法)来做到这一点而不使其同步。

就像之前提到的 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)

0

您将需要知道如何识别结果中的错误。如果没有标准的预期错误,建议您对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
  }

0

这不是错误日志的最佳方法,但是您始终可以将所有内容设置为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;

-1

这是怎样Promise.all的设计工作。如果一个承诺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);
        });
    });
}


不使用reject()诺言就可以了,但是如果您需要使用另一个库的诺言怎么办?
丹·达斯卡斯库

-8

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

它是如何工作的?请显示并说明您对该功能的实现。
Bergi

我写了一个新的并发逻辑,例如Promise.all。但是它将收集所有数据和每个承诺的错误。它也支持对象输入,这不是重点。收集所有数据和错误后,我将重写该promise.then方法以处理注册的回调,包括被拒绝和已实现。有关详细信息,请参见代码
wenshin

嗯,该代码将调用onFulfilledonRejected传递给的处理程序then
Bergi '16

是的,仅当承诺状态混合为fulfilled和时rejected。但实际上它造成一个困难的问题是所有承诺的用例兼容通常情况下,像onFulfilledonRejected所有返回Promise.reject()Promise.resolve()。到目前为止,我不清楚如何解决它,有人有更好的主意吗?目前有问题的最佳答案是,它可能无法过滤浏览器环境中的数据和错误。
温辛

我们是否需要使用pip python软件包管理器安装npm模块?
sevenfourk
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.