Как я могу ожидать нескольких promises в параллельном режиме без "неудачного" поведения?
Я использую async
/await
для параллельного вызова нескольких api
:
async function foo(arr) {
const results = await Promise.all(arr.map(v => {
return doAsyncThing(v)
}))
return results
}
Я знаю, что, в отличие от loops
, Promise.all
выполняется в параллельном режиме (то есть часть ожидающего результата находится параллельно).
Но Я также знаю, что:
Promise.all отклоняется, если один из элементов отклонен и Promise.all не работает быстро: если у вас есть четыре promises, которые разрешаются после тайм-аут, и один немедленно отклоняется, тогда Promise.all отклоняет немедленно.
Как я читал, если я Promise.all
с 5 promises, а первый для завершения возвращает a reject()
, то остальные 4 будут эффективно отменены и их обещанные значения resolve()
будут потеряны.
Есть ли третий способ? Где выполнение эффективно параллельно, но один отказ не портит всю связку?
Ответы
Ответ 1
Использование catch
означает, что обещание разрешается (если вы не выбросите исключение из catch
или вручную отклоните цепочку обещаний), поэтому вам не нужно явно возвращать разрешенные обещания IIUC.
Это означает, что просто обрабатывая ошибки с помощью catch
, вы можете достичь того, что хотите.
Если вы хотите стандартизировать способ обработки отклонения, вы можете применить функцию обработки отклонения ко всем promises.
async function bar() {
await new Promise(r=> setTimeout(r, 1000))
.then(()=> console.log('bar'))
.then(()=> 'bar result');
}
async function bam() {
await new Promise((ignore, reject)=> setTimeout(reject, 2000))
.catch(()=> { console.log('bam errored'); throw 'bam'; });
}
async function bat() {
await new Promise(r=> setTimeout(r, 3000))
.then(()=> console.log('bat'))
.then(()=> 'bat result');
}
function handleRejection(p) {
return p.catch(err=> ({ error: err }));
}
async function foo(arr) {
console.log('foo');
return await Promise.all([bar(), bam(), bat()].map(handleRejection));
}
foo().then(results=> console.log('done', results));
Ответ 2
В то время как техника в принятом ответе может решить вашу проблему, это анти-шаблон. Решение обещания с ошибкой не является хорошей практикой, и есть более чистый способ сделать это.
То, что вы хотите сделать, это псевдоязык:
fn task() {
result-1 = doAsync();
result-n = doAsync();
// handle results together
return handleResults(result-1, ..., result-n)
}
Это можно сделать просто с помощью async
/await
без использования Promise.all
. Рабочий пример:
console.clear();
function wait(ms, data) {
return new Promise( resolve => setTimeout(resolve.bind(this, data), ms) );
}
/**
* This will be runned in series, because
* we call a function and immediately wait for it result,
* so this will finish in 1s.
*/
async function series() {
return {
result1: await wait(500, 'seriesTask1'),
result2: await wait(500, 'seriesTask2'),
}
}
/**
* While here we call the functions first,
* then wait for the result later, so
* this will finish in 500ms.
*/
async function parallel() {
const task1 = wait(500, 'parallelTask1');
const task2 = wait(500, 'parallelTask2');
return {
result1: await task1,
result2: await task2,
}
}
async function taskRunner(fn, label) {
const startTime = performance.now();
console.log(`Task ${label} starting...`);
let result = await fn();
console.log(`Task ${label} finished in ${ Number.parseInt(performance.now() - startTime) } miliseconds with,`, result);
}
void taskRunner(series, 'series');
void taskRunner(parallel, 'parallel');