forEach/map и async не идут рука об руку

May 07 2023
Иногда мы часто склонны передавать асинхронные функции с функциями forEach и map. Например: Результат: Все числа от 1.
Заголовок

Иногда мы часто склонны передавать асинхронные функции с функциями forEach и map.

Например:

[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));

Результат: Все числа от 1 до 5 печатаются одно за другим, нопосле печати каждой строки нет интервала в 1 секунду .

Почему это происходит?

Посмотрите на эту функцию javascipt:

async function doubleOf(n) {
  return n*2;
}

Результат: на самом деле это возвращает обещание, которое разрешается в число.

Если мы напишем эквивалент этой функции на машинописном языке со строгими типами, это прояснит ситуацию.

Следующий код не будет компилироваться:

async function doubleOf(n: number): number {
  return n*2;
}

Правильная версия будет:

async function doubleOf(n: number): Promise<number> {
  return n*2;
}

Не дайте себя одурачить синтаксическим сахаром, предоставляемым async-await. Если бы мы написали чистые промисы без использования асинхронности, вышеприведенная функция выглядела бы так:

function doubleOf(n) {
  return new Promise((resolve) => resolve(n*2));
}

function doubleOf(n: number): Promise<number> {
  return new Promise((resolve) => resolve(n*2));
}

асинхронные функции возвращают обещания, а не значения
  1. У нас есть doubleOfфункция, которая принимает число и возвращает число. Обычный старый javascript.
  2. У нас есть doubleOfOldWayфункция, которая принимает число и возвращает обещание, которое разрешается в число.
  3. У нас есть doubleOfNewWayасинхронная функция, которая принимает число и кажется, что возвращает число, но на самом деле она возвращает обещание, которое разрешается в число, как doubleOfOldWayфункция.
  4. doubleOfOldWayи doubleOfNewWayфункции точно такие же.
  5. И, следовательно, когда мы пытаемся выполнить операцию умножения над значениями, возвращаемыми функциями doubleOfOldWayи doubleOfNewWay, результатом будет NaN, поскольку мы не можем выполнять несколько промисов (очевидно!).
  6. Чтобы умножить doubleOfOldWayи doubleOfNewWay:

Итак, вернемся к нашему первоначальному примеру:

[1,2,3,4,5].forEach(async (n) => setTimeout(console.log(n), 1000));

Самый правильный способ реализовать то, что мы ожидаем от этой функции forEach, — использовать простой цикл for:

for(const number of [1,2,3,4,5]) {
    console.log(number);
    await new Promise(resolve => setTimeout(resolve, 1000)); // Sleep for "atleast" 1 second
}

[1,2,3,4,5].map(async (n) => n*2);

(5) [Promise, Promise, Promise, Promise, Promise]
0: Promise {<fulfilled>: 2}
1: Promise {<fulfilled>: 4}
2: Promise {<fulfilled>: 6}
3: Promise {<fulfilled>: 8}
4: Promise {<fulfilled>: 10}

Чтобы получить список двойных значений каждого числа, мы можем сделать следующее:

await Promise.all([1,2,3,4,5].map(async (n) => n*2));

[2, 4, 6, 8, 10]