Комментарии 32
upd: вчитался, реально походу какую то статью запихнули в переводчик…
Функция inParallel выполняется последовательно(за 1 секунду). Чтобы она выполнилась параллельно нужно убрать await перед вызовом функций:
async function inParallel() {
const await1 = pause500ms();
const await2 = pause500ms();
await await1;
await await2;
console.log("I will be logged after 500ms");
}
Если просто убрать await, то все равно будет неверно, т.к. console.log сработает сразу синхронно (можете проверить в консоле браузера). Правильный вариант опять же будет с промисами:
async function inParallel() {
console.time("I will be logged after");
const await1 = pause500ms();
const await2 = pause500ms();
await Promise.all([await1, await2]);
console.timeEnd("I will be logged after"); // => I will be logged after: 500.319ms
}
async function inParallel() {
const await1 = await pause500ms();
const await2 = pause500ms();
await await1;
await await2;
console.log("I will be logged after 1000ms");
}
async function inParallel() {
const await1 = pause500ms();
const await2 = await pause500ms();
await await1;
await await2;
console.log("I will be logged after 500ms");
}
async function inParallel() {
const await1 = await pause500ms(); // Ждем 500 мс и записываем результат (undefined) в await1
const await2 = pause500ms(); // Записываем промис, который тут же начинает выполняться
await await1; // Ждем await1 (undefined) => пропускаем
await await2; // Ждем await1 (pause500ms()) => Ждем еще 500 мс
console.log("I will be logged after 1000ms");
}
Соответственно,
async function inParallel() {
const await1 = pause500ms(); // Записываем промис, который тут же начинает выполняться
const await2 = await pause500ms(); // Ждем 500 мс и записываем результат (undefined) в await1; к этому времени await1 уже также закончился.
await await1; // Ждем await1 (pause500ms()), который закончился => идем дальше
await await2; // Ждем await2 (undefined) => пропускаем
console.log("I will be logged after 500ms");
}
И в итоге, в первом случае мы инициируем первый таймаут и дожидаемся когда он разрешится. После чего инициируем второй и в 5 строчке снова ждём. Во втором случае мы инициируем первый таймаут, инициируем второй таймаут и ждём пока второй разрешится, т.е. оба промайса разрешаются одновременно. Что важно, они не выполняют код одновременно(у нас однопоточная модель), они оба ждут какого-то внешнего события.
Простите мою невнимательность, я думал, вы цитируете код из статьи, и убрал await совсем. Моя ошибка.
Кажется, в комментариях выше разбираюстся в тонкостях async/await лучше, чем в самой статье.
Уважаемый переводчик, почему вы решили переводить именно эту не самую лучшую статью?
await await2;
К сожалению, этот код будет работать не на 100% корректно. Представьте, что await1 резолвнется (resolve) через 1000мс, а await2 реджектнется (reject) через 500мс. Пока мы ждем await await1, мы НЕ ждем await await2. Таким образом, await2 отработает как unhandled rejection. И мы не сможем поймать ошибку (которая reject(error) внутри await2) в try-catch выше по стеку.
Promise.all([promise1, promise2]) единственный способ когда нужно ждать несколько промисов одновременно. Так как Promise.all начинает ждать (await) все промисы сразу, то и ошибку можно поймать от любого ожидаемого промиса.
async function badAsync(){
await await1; // стартовал первый промис
// первый промис выполняется, второй ещё нет, ждём
// первый промис зарезолвился через 1000 мс
await await2; // первый уже зарезолвился, стартовали второй
// ждём второй
// второй падает через 500 мс
}
async function test(){
try{
await badAsync();
} catch(err){
// поймали reject от await2 через 1500мс
}
}
Итого через 1500 мс я поймал ошибку от второго промиса, и по-моему это корректно, так как я по каким-то причинам не мог выполнить второй запрос параллельно с первым (например ожидал его результата). Ничего не потерял, или Вы о чём-то другом?
Для меня
Пока мы ждем await await1, мы НЕ ждем await await2. Таким образом, await2 отработает как unhandled rejection
Звучит как минимум странно, потому что мы ещё не дошли до вызова и выполнения await2
при последовательном вызове, и потому он не мог выполниться раньше. И уж тем более он ни как не мог стать unhandled rejection
Я говорю о таком коде:
const await1 = new Promise((res) => setTimeout(res, 1000));
const await2 = new Promise((_, rej) => setTimeout(rej, 500));
await await1;
await await2;
Хорошо, 2 параллельных запроса, но с синхронным ожиданием их результата.
Ваше утверждение про неуловимые исключения по-прежнему не соответствует коду
await2 отработает как unhandled rejection. И мы не сможем поймать ошибку (которая reject(error) внутри await2) в try-catch выше по стеку.
async function test(){
const await1 = new Promise((res) => setTimeout(res, 1000));
const await2 = new Promise((_, rej) => setTimeout(()=>rej(new Error("myError")), 500));
console.log("1"); // я буду выведена в консоль сразу
await await1;
console.log("2"); // я только через 1000 мс
await await2; // я выкину ошибку, которая была создана 500 мс назад
console.log("3"); // меня не напечатают
}
async function test2(){
try{
await test();
} catch(err){
console.log("Catched!", err); // Ошибка из await2
}
}
Ещё можно было внутри test
сделать try catch, тоже бы сработал. Я полностью согласен, что Promise.all тот самый правильный способ для параллельных запросов, но не мог не заметить ошибку про потерянные исключения. Хотя она встречается если забыть await
Так как нас даже warning предупреждает (In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code) что unhandled rejection это фатально, то для эмуляции будущих версий (и для fail fast, без unpredicted state) я выполняю per-process что-то типа
process.on('unhandledRejection', (error) => {
console.error('>>> unhandledRejection pid', process.pid);
console.error(error);
process.exit(6);
});
Поэтому у автора два bad practice. Первый — пропускать unhandled rejection, а второй — использовать два await вместо Promise.all. Конечно, если пропускать unhandled rejection, то можно catch если есть await, но чаще всего await просто забыт.
использовать два await вместо Promise.all
Кстати, да, удивляет, что в стандарте нет простого и лаконичного решения:
const arr = await [await1, await2];
const arr = await Promise.all([ await1, await2 ]);
именно это и делает. Promise.all создает общий промис, а await ждет его.
Это понятно, я имел ввиду короткую запись.
Короткая запись тоже невозможна. По стандарту await возвращает значение если оно не является промисом. await [ 15, 17 ]
должен вернуть массив [ 15, 17 ]
, не заглядывая внутрь него. Заглядывать внутрь массива — значит тратить время на то, что программисту не нужно. Если программисту нужно явно ждать все промисы массива, на это есть Promise.all.
Попробовал в консоли такой код, и он работает.
const arr = [await await1, await await2];
Как мне показалось, работает эквивалентно await Promise.all. Поправьте, если я не прав.
Работает, но не параллельно, а последовательно. И я боюсь, что, как и у автора, unhandled rejection. Полностью как у автора, только в массиве.
Промисы были инициализированы выше по коду (сейчас в состоянии pending), т.ч. получается параллельно (если такое слово вообще применимо к JS), и время ожидания равно ожиданию дольшего из них, как и в случае с Promise.all. Специально тестировал с разными таймаутами.
Промисы были инициализированы выше по коду (сейчас в состоянии pending), т.ч. получается параллельно
Природа event-loop не даст войти внутрь промиса пока не появится в коде «свободное окно» вроде then/await или отсутствие других вычислений, пока они именно что в ожидании на выполнение
async function delay(time) {
return new Promise(resolve => setTimeout(resolve, time))
}
async function main() {
try {
console.time('await array')
let arr = [await delay(500), await delay(1000)]
console.timeEnd('await array')
console.time('Promise.all')
arr = await Promise.all([delay(500), delay(1000)])
console.timeEnd('Promise.all')
}
catch(err) {
console.log(err)
}
}
main()
>node index.js
await array: 1501.689ms
Promise.all: 1000.757ms
const await1 = delay(500)
const await2 = delay(500)
let arr = [await await1, await await2]
await array: 501.792ms
Promise.all: 500.836ms
То всё работает, но для второго await остается Unhandled promise rejection
Насколько я помню стандарт, выполнение Promise помещается в начало стека event-loop (так называемые microtasks), никакого "окна" ждать не надо, но тогда в случае двух таких промиссов try-catch бесполезен, т.к. вы верно заметили — будет unhandledRejection.
Уже понял, что сахар искать бесполезно, остается только старый-добрый Promise.all Спасибо за разъяснения.
Одним из основных преимуществ JavaScript является то, что всё асинхронноОчень смелое заявление. Так уж всё? Ну, кроме http-запросов и таймаутов?
node.js != js
В JS асинхронно всё, особенно console.log(); )
Автору было бы не плохо перед async/await посмотреть JSConf EU 2014 про call stack и callback queue.
Асинхронные функции 101