Sometimes it is necessary to block the code for a period of time using the Sleep function, which is routinely implemented and called in the following way.

1
2
3
4
5
6
7
8
// Sleep Function
const sleep = (ms) =>
  new Promise((resolve) => setTimeout(resolve, ms))(
    // Usage
    async () => {
      await sleep(3000);
    }
  );

However, when used in Array.prototype.map, it behaves incorrectly, as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// code snippet 1
[1, 2].map(async (num) => {
  console.log("Loop Start");
  console.log(num);
  await sleep(3000);
  console.log("Loop End");
});

// expected output
//    Loop Start
//    1
//        Wait for about 3s
//    Loop End
//    Loop Start
//    2
//        Wait for about 3s
//    Loop End

// Actual output
//    Loop Start
//    1
//    Loop Start
//    2
//         Wait for about 3s
//    Loop End
//    Loop End

The expectation is that each loop will pause for about 3s before continuing; however, the actual performance is that each time await sleep(3000); is executed, it goes to the next loop without waiting for the result to return. The reason for this incorrect performance is.

When the async function is executed, it immediately returns the pending state of the Promise (Promise is Truthy)! Therefore, in a map loop, you don’t wait for the await operation to complete, but go directly to the next loop, so you should use async in conjunction with a for loop.

To verify, let’s modify code snippet 1.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// code snippet 2
const sleep = (ms) =>
  new Promise((resolve) => {
    console.log("sleep");
    setTimeout(() => {
      console.log("resolve");
      resolve();
    }, ms);
  });

const mapResult = [1, 2].map(async (num) => {
  console.log("Loop Start");
  console.log(num);
  await sleep(3000);
  console.log("Loop End");
});

console.log("mapResult", mapResult);

// Actual output
//    Loop Start
//    1
//    sleep
//    Loop Start
//    2
//    sleep
//    mapResult [ Promise { <pending> }, Promise { <pending> } ]
//    resolve
//    Loop End
//    resolve
//    Loop End

That is, an array containing multiple Promises with the status pending!

Alternatively, if you are just looping and don’t need to manipulate the array returned by map, then you should also use a for loop.

For forEach, see its Polyfill in MDN, if the callback function is asynchronous, it will behave concurrently because it doesn’t support waiting for asynchronous operations to complete before moving on to the next loop.

Thanks to @YangNing for the method solved using Array.prototype.reduce.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// https://codepen.io/juzhiyuan/pen/jONwyeq

const sleep = (wait) => new Promise((resolve) => setTimeout(resolve, wait));

const __main = async function () {
  // 你的需求其实是在一组 task 中,循环执行,每个都 sleep,并返回结果
  const tasks = [1, 2, 3];

  let results = await tasks.reduce(async (previousValue, currentValue) => {
    // 这里是关键,需要 await 前一个 task 执行的结果
    // 实际上每个 reduce 每个循环执行都相当于 new Promise
    // 但第二次执行可以拿到上一次执行的结果,也就是上一个 Promise
    // 每次执行只要 await 上一个 Promise,即可实现依次执行
    let results = await previousValue;
    console.log(`task ${currentValue} start`);
    await sleep(1000 * currentValue);
    console.log(`${currentValue}`);
    console.log(`task ${currentValue} end`);
    results.push(currentValue);
    return results;
  }, []);

  console.log(results);
};

__main();

// Actual output:
//    task 1 start
//    1
//    task 1 end
//    task 2 start
//    2
//    task 2 end
//    task 3 start
//    3
//    task 3 end
//    [1, 2, 3]

sobyte