Ожидание более чем одной параллельной операции ожидания
Как я могу изменить следующий код, чтобы обе асинхронные операции запускались и давали возможность работать одновременно?
const value1 = await getValue1Async();
const value2 = await getValue2Async();
// use both values
Мне нужно сделать что-то подобное?
const p1 = getValue1Async();
const p2 = getValue2Async();
const value1 = await p1;
const value2 = await p2;
// use both values
3 ответа
TL;DR
Не используйте шаблон в вопросе, где вы получаете обещания, а затем отдельно ждите их; вместо этого используйте Promise.all
(по крайней мере на данный момент):
const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
Хотя ваше решение выполняет две операции параллельно, оно не обрабатывает отклонение должным образом, если оба обещания отклоняются.
Подробности:
Ваше решение запускает их параллельно, но всегда ждет завершения первого, а затем ожидания второго. Если вы просто хотите запустить их, запустить их параллельно и получить оба результата, это просто прекрасно. (Нет, не надо, продолжайте читать...) Обратите внимание, что если первая занимает (скажем) пять секунд, а вторая терпит неудачу в течение одной секунды, ваш код будет ждать полные пять секунд, а затем потерпит неудачу.
К сожалению, в настоящее время нет await
синтаксис для параллельного ожидания, так что у вас есть неловкость, которую вы перечислили, или Promise.all
, (Там было обсуждение await.all
или похожий, хотя; возможно когда-нибудь.)
Promise.all
версия:
const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
... что более кратко, и также не ожидает завершения первой операции, если вторая не удалась быстро (например, в моем примере пять секунд / одна секунда выше, вышеприведенное будет отклонено в одну секунду, а не в ожидании пяти), Также обратите внимание, что в исходном коде, если второе обещание отклоняется до того, как первое обещание разрешается, вы вполне можете получить ошибку "необработанное отклонение" в консоли (в настоящее время вы используете Chrome v61), хотя эта ошибка может быть ложной (поскольку вы сделать, в конце концов, справиться с отказом). Но если оба обещания отклоняются, вы получите настоящую необработанную ошибку отклонения, потому что поток контроля никогда не достигает const value2 = await p2;
и, таким образом, отклонение p2 никогда не обрабатывается.
Необработанные отклонения - это плохая вещь ™ (настолько, что скоро NodeJS прервет процесс на действительно необработанных отклонениях, точно так же, как необработанные исключения - потому что это то, чем они являются), поэтому лучше всего избегать "получите обещание" await
это "образец в вашем вопросе.
Вот пример разницы во времени в случае сбоя (с использованием 500 мс и 100 мс, а не 5 секунд и 1 секунда) и, возможно, также, возможно, ложная необработанная ошибка отклонения (откройте реальную консоль браузера, чтобы увидеть ее):
const getValue1Async = () => {
return new Promise(resolve => {
setTimeout(resolve, 500, "value1");
});
};
const getValue2Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, "error");
});
};
// This waits the full 500ms before failing, because it waits
// on p1, then on p2
(async () => {
try {
console.time("separate");
const p1 = getValue1Async();
const p2 = getValue2Async();
const value1 = await p1;
const value2 = await p2;
} catch (e) {
console.error(e);
}
console.timeEnd("separate");
})();
// This fails after just 100ms, because it doesn't wait for p1
// to finish first, it rejects as soon as p2 rejects
setTimeout(async () => {
try {
console.time("Promise.all");
const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
} catch (e) {
console.timeEnd("Promise.all", e);
}
}, 1000);
Open the real browser console to see the unhandled rejection error.
И здесь мы отвергаем оба p1
а также p2
, что приводит к не поддельной необработанной ошибке отклонения на p2
:
const getValue1Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 500, "error1");
});
};
const getValue2Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, "error2");
});
};
// This waits the full 500ms before failing, because it waits
// on p1, then on p2
(async () => {
try {
console.time("separate");
const p1 = getValue1Async();
const p2 = getValue2Async();
const value1 = await p1;
const value2 = await p2;
} catch (e) {
console.error(e);
}
console.timeEnd("separate");
})();
// This fails after just 100ms, because it doesn't wait for p1
// to finish first, it rejects as soon as p2 rejects
setTimeout(async () => {
try {
console.time("Promise.all");
const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
} catch (e) {
console.timeEnd("Promise.all", e);
}
}, 1000);
Open the real browser console to see the unhandled rejection error.
В комментарии вы спросили:
Дополнительный вопрос: будет ли следующая сила ждать обоих (и отбрасывать результаты)
await p1 && await p2
?
При отклонении обещания возникают те же проблемы, что и в исходном коде: p1
разрешается, даже если p2
отклоняет ранее; это может привести к ошибочно необоснованной ошибке отклонения, если p2
отвергает раньше p1
решает; и он генерирует подлинную необработанную ошибку отклонения, если оба p1
а также p2
отклонить (потому что p2
Отклонение никогда не обрабатывается).
Вот тот случай, когда p1
решает и p2
отвергает:
const getValue1Async = () => {
return new Promise(resolve => {
setTimeout(resolve, 500, false);
});
};
const getValue2Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, "error");
});
};
(async () => {
try {
const p1 = getValue1Async();
const p2 = getValue2Async();
console.log("waiting");
await p1 && await p2;
} catch (e) {
console.error(e);
}
console.log("done waiting");
})();
Look in the real console (for the unhandled rejection error).
... и где оба отвергают:
const getValue1Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 500, "error1");
});
};
const getValue2Async = () => {
return new Promise((resolve, reject) => {
setTimeout(reject, 100, "error2");
});
};
(async () => {
try {
const p1 = getValue1Async();
const p2 = getValue2Async();
console.log("waiting");
await p1 && await p2;
} catch (e) {
console.error(e);
}
console.log("done waiting");
})();
Look in the real console (for the unhandled rejection error).
Я думаю, что это должно работать:
const [value1, value2] = await Promise.all([getValue1Async(),getValue2Async()]);
Более подробный пример приведен ниже, если он помогает понять:
const promise1 = async() => {
return 3;
}
const promise2 = async() => {
return 42;
}
const promise3 = async() => {
return 500;
// emulate an error
// throw "something went wrong...";
}
const f1 = async() => {
try {
// returns an array of values
const results = await Promise.all([promise1(), promise2(), promise3()]);
console.log(results);
console.log(results[0]);
console.log(results[1]);
console.log(results[2]);
// assigns values to individual variables through 'array destructuring'
const [value1, value2, value3] = await Promise.all([promise1(), promise2(), promise3()]);
console.log(value1);
console.log(value2);
console.log(value3);
} catch (err) {
console.log("there was an error: " + err);
}
}
f1();
Используйте.catch() и Promise.all()
Убедитесь, что вы правильно обрабатываете отклонения, и вы можете безопасно использовать Promises.all(), не сталкиваясь с необработанными отклонениями. (Изменить: разъяснение за обсуждение: не ошибка unhandled rejection
но просто отклонения, которые не обрабатываются кодом. Promise.all()
скину первое обещание отклонения и проигнорирую остальных).
В приведенном ниже примере возвращается массив [[error, results], ...], чтобы упростить обработку результатов и / или ошибок.
let myTimeout = (ms, is_ok) =>
new Promise((resolve, reject) =>
setTimeout(_=> is_ok ?
resolve(`ok in ${ms}`) :
reject(`error in ${ms}`),
ms));
let handleRejection = promise => promise
.then((...r) => [null, ...r])
.catch(e => [e]);
(async _=> {
let res = await Promise.all([
myTimeout(100, true),
myTimeout(200, false),
myTimeout(300, true),
myTimeout(400, false)
].map(handleRejection));
console.log(res);
})();
Вы можете бросить из catch (), чтобы остановить ожидание всех (и отменить результаты остальных), однако - вы можете делать это только один раз за блоки try/catch, поэтому необходимо поддерживать и проверять флаг has_thorwn, чтобы убедиться, что никаких необработанных ошибок не происходит.
let myTimeout = (ms, is_ok) =>
new Promise((resolve, reject) =>
setTimeout(_=> is_ok ?
resolve(`ok in ${ms}`) :
reject(`error in ${ms}`),
ms));
let has_thrown = false;
let handleRejection = promise => promise
.then((...r) => [null, ...r])
.catch(e => {
if (has_thrown) {
console.log('not throwing', e);
} else {
has_thrown = 1;
throw e;
}
});
(async _=> {
try {
let res = await Promise.all([
myTimeout(100, true),
myTimeout(200, false),
myTimeout(300, true),
myTimeout(400, false)
].map(handleRejection));
console.log(res);
} catch(e) {
console.log(e);
}
console.log('we are done');
})();
Решает вместо обещаний
const wait = (ms, data) => new Promise( resolve => setTimeout(resolve, ms, data) )
const reject = (ms, data) => new Promise( (r, reject) => setTimeout(reject, ms, data) )
const e = e => 'err:' + e
const l = l => (console.log(l), l)
;(async function parallel() {
let task1 = reject(500, 'parallelTask1').catch(e).then(l)
let task2 = wait(2500, 'parallelTask2').catch(e).then(l)
let task3 = reject(1500, 'parallelTask3').catch(e).then(l)
console.log('WAITING')
;[task1, task2, task3] = [await task1, await task2, await task3]
console.log('FINISHED', task1, task2, task3)
})()
Как указывалось в других ответах, отклоненное обещание может привести к необработанному исключению.
Вот этот.catch(e => e)
это хитрый маленький трюк, который ловит ошибку и передает ее по цепочке, позволяя обещанию resolve
, вместо rejecting
,
Если вы найдете этот код ES6 некрасивым, посмотрите дружелюбнее здесь.