处理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.all返回它找到的第一个错误(忽略其余的错误),然后我就无法从数组中的其他承诺(没有出错)中获得数据。

我试过做一些像…

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调用(路由。异步调用)

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之前似乎达到了从原始promise中捕获任何错误的目的,但随后将整个数组返回到下一个.then

谢谢!

348589 次浏览

Promise.all是全或无。一旦数组中的所有promise都被解析,它就会被解析,或者当其中的一个拒绝时就会被拒绝。换句话说,它要么使用所有已解析值的数组进行解析,要么使用单个错误进行拒绝。

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

你的代码

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

我能想到为什么它会“不解决”的唯一原因是它在代码中失败了,你没有向我们展示,而你没有看到任何关于此的错误消息的原因是因为这个承诺链没有以最终捕获终止(就你展示给我们的而言)。

我冒昧地从您的示例中提出了“现有链”,并用catch终止了该链。这可能不适合你,但对于阅读这篇文章的人来说,总是返回或终止链是很重要的,否则潜在的错误,甚至是编码错误,将被隐藏(这就是我怀疑在这里发生的事情):

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 < / p >

安装

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. allsettle()方法可以解决这个问题 您可以根据每个承诺的状态来处理它,可以是完全提交的,也可以是拒绝的 所以< / p >

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的实用函数。这个实用函数返回一个带有resultserrors的对象。

它的思想是,你传递给executeAllPromises的所有Promise都会被包装成一个新的Promise,这个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}`);
});

新回答

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

未来承诺API

这就是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);
});
});
}

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

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

你考虑过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(),你可以从杰克阿奇博尔德: Promise.all(promises.map(p => p.catch(() => undefined)));使用这个变通方法

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

使用Async await -

这里一个异步函数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]

或者,如果你有这样的情况,当有一个失败时,你并不特别关心已解决的承诺的值,但你仍然希望它们运行,你可以这样做,当它们都成功时,你会正常地解决承诺,当它们中的任何一个失败时,你会拒绝失败的承诺:

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

你总是可以用一种方式来包装你的promise返回函数,让它们捕获失败并返回一个一致的值(例如error.message),这样异常就不会一直滚到promise。所有功能和禁用它。

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给你一个信号,当所有的输入承诺都被解决了,这意味着它们要么被满足要么被拒绝。这在不关心承诺状态的情况下很有用,您只想知道工作何时完成,而不管它是否成功。

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.allSettled

而不是承诺。都使用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));

Polyfill

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

的承诺。用过滤器解决

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