Pull to refresh

Comments 32

В чем заключается посыл статьи? Я привел выдержки из общедоступной документации со своими размышлениями на тему? И еще непонятно в тексте есть «Как я уже упоминала», а автор вроде как мальчик. И что такое 101?
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
}
Проверил ещё раз. Исправленный мной код работает нормально. У нас разные консоли?
Согласен Kot_DaVinchi, ваш код работает нормально, но если пойти дальше объясните плз почему время исполнения разное?

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");
}
Вызов функции pause500ms инициирует setTimeout и возвращает promice в состоянии pending(в состоянии когда результата ещё нет). Директива await дожидается когда promice будет в состоянии resolve(или reject).
И в итоге, в первом случае мы инициируем первый таймаут и дожидаемся когда он разрешится. После чего инициируем второй и в 5 строчке снова ждём. Во втором случае мы инициируем первый таймаут, инициируем второй таймаут и ждём пока второй разрешится, т.е. оба промайса разрешаются одновременно. Что важно, они не выполняют код одновременно(у нас однопоточная модель), они оба ждут какого-то внешнего события.

Простите мою невнимательность, я думал, вы цитируете код из статьи, и убрал await совсем. Моя ошибка.

Кажется, в комментариях выше разбираюстся в тонкостях async/await лучше, чем в самой статье.


Уважаемый переводчик, почему вы решили переводить именно эту не самую лучшую статью?

Суть в том, чтобы у людей возник интерес. Люди начинают обсуждать, объяснять друг другу. Это же замечательно.

await await1;
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

У вас разве в логе нет после единицы UnhandledPromiseRejectionWarning?

Я не случайно в reject добавил ошибку к вашему коду, а потом залогировал то, что я поймал в test2.

Тогда поясню свою позицию про try-catch.
Так как нас даже 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
Хотя нет, если написать именно как iShatokhin предлагает:
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
Висят 2 прищепки, особенно левая =>
В JS асинхронно всё, особенно console.log(); )
Автору было бы не плохо перед async/await посмотреть JSConf EU 2014 про call stack и callback queue.
Sign up to leave a comment.

Articles