Asynchronous traversal of JS, can you really write it?

Offer arrives, dig friends to pick up! I am participating in the 2022 Spring Recruitment Check-In Event, click to view the event details .

Sometimes we need to traverse the elements of the array and pass them into the asynchronous function for execution. The asynchronous writing method is easy to write wrong. Let's take a look at the error-prone points.

Suppose we have an asynchronous method sleepPromise of the form:

function sleepPromise(msg, t{
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`promise-${msg} ${t}s`);
    }, t * 1000);
  })
}
复制代码

For the convenience of demonstration, the sleep method in the form of promise is written using setTimeout. The incoming t is the delay execution time, and msg is the information content.

In actual development, the asynchronous method may be to pass in the user's friend id to look up the database and obtain simple friend information.

Suppose we need to write an asynchronous convenience implementation below the commented position in the code below.

async function loopAsync({
  console.log('[start]');
  const curTime = Date.now();

  const tasks = [
    ['a'3],
    ['b'1],
    ['c'2],
  ];
  
  // 下面写遍历 tasks 传入到异步方法的实现

  console.log(`[end] duration ${((Date.now() - curTime) / 1000).toFixed(2)}s`);
}
复制代码

Wrong spelling: forEach

Usually the front end uses forEach as soon as it sees that the array is to be traversed. If you're not sophisticated enough, you might write an implementation like this:

// 错误的 forEach 写法
tasks.forEach(async task => {
  const [ msg, t ] = task;
  const m = await sleepPromise(msg, t);
  console.log(m);
})
复制代码

The output is;

[start]
[end] duration 0.00s
promise-b 1s
promise-c 2s
promise-a 3s
复制代码

This way of writing is not correct, in fact, the traversal is written as synchronization.

What's the problem? Because forEach itself does not support asynchronous writing, it is invalid whether you add the await keyword in front of the forEach method, because there is no asynchronous logic in it.

forEach is an ES5 API, much earlier than ES6 Promise. For backward compatibility, forEach will also not support asynchronous processing in the future.

Therefore, the execution of forEach will not block the code after loopAsync, so it will cause blocking failure and output first  [end].

Serial writing: for loop

// 串行写法
for (const task of tasks) {
  const [ msg, t ] = task;
  const m = await sleepPromise(msg, t);
  console.log(m);
}
复制代码

Using the ordinary for loop writing method, the outer function of await is still the loopAysnc method, and the blocking code can be saved correctly.

But the problem here is that the execution of these asynchronous methods is  serial  . It can be seen that a total of 6 s were executed.

[start]
promise-a 3s
promise-b 1s
promise-c 2s
[end] duration 6.01s
复制代码

If our requests have sequential dependencies, this is no problem.

But if our scenario is to look up the corresponding username from the database based on the user id array, our time complexity  O(n) is unreasonable.

At this point, we need to rewrite it into  parallel  asynchrony, and also ensure that all asynchrony is executed before executing the next step. we can use  Promise.all().

Parallel implementation: Promise.all

// 并行写法
const taskPromises = tasks.map(task => {
  const [ msg, t ] = task;
  return sleepPromise(msg, t).then(m => {
    console.log(m);
  });
});
await Promise.all(taskPromises);
复制代码

First, we need to generate the corresponding promise object array based on the tasks array, and then pass it into the Promise.all method for execution.

This way, these asynchronous methods are executed concurrently. When all the asynchronous execution is completed, the code is executed down.

The output is as follows:

[start]
promise-b 1s
promise-c 2s
promise-a 3s
[end] duration 3.00s
复制代码

It's over in 3 seconds, too strong.

back to forEach

As mentioned earlier, the bottom layer of forEach does not implement asynchronous processing, which leads to blocking failure, so we might as well implement a simple forEach that supports asynchronous.

Parallel implementation:

async function forEach(arr, fn{
  const fns = [];
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i];
    fns.push(fn(item, i, arr));
  }
  await Promise.all(fns);
}
复制代码

Serial implementation:

async function forEach(arr, fn{
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i];
    await fn(item, i, arr);
  }
}
复制代码

usage:

await forEach(tasks, async task => {
  const [ msg, t ] = task;
  const m = await sleepPromise(msg, t);
  console.log(m);
})
复制代码

Summarize

Briefly summarize.

Generally speaking, we use the asynchronous method of Promise.all to execute in parallel, which is often used in the scenario where the database searches for data corresponding to some ids.

The serial writing method of the for loop is suitable for multiple asynchronous situations with dependencies, such as finding the final referrer.

forEach is a pure typo, unless it's not necessary to use async/await.

I am the front-end watermelon brother, focusing on sharing front-end knowledge, welcome to follow me.

Guess you like

Origin juejin.im/post/7078695117967589406