How to work with async / await in javascript cycles

Original author: Anton Lavrenov
  • Transfer
  • Tutorial

How to run asynchronous loops in order or in parallel in JavaScript?


Before doing asynchronous magic, I want to remind you of what classic synchronous loops look like.


Synchronous loops


A long time ago, I wrote cycles in this way (maybe you too):


for (var i=0; i < array.length; i++) {
  var item = array[i];
  // делаем что-нибудь с item
}

This cycle is good and fast. But he has a lot of problems with readability and support. After a while, I got used to his better version:


array.forEach((item) => {
  // делаем что-нибудь с item
});

JavaScript language is developing very fast. There are new features and syntax. One of my favorite improvements is async / await .


Now I use this syntax quite often. And sometimes there are situations when I need to do something with the elements of the array asynchronously.


Asynchronous loops


How to use awaitin a body loop? Let's just try to write an asynchronous function and wait for the processing task of each element:


asyncfunctionprocessArray(array) {
  array.forEach(item => {
    // тут мы определили синхронную анонимную функцию// НО ЭТО КОД ВЫДАСТ ОШИБКУ!await func(item);
  })
}

This code will generate an error. Why? Because we cannot use the awaitsynchronous function inside. As you can see processArray, this is an asynchronous function. But the anonymous function we use for forEachis synchronous .


What can you do about it?


1. Do not wait for the execution result


We can define an anonymous function as asynchronous:


asyncfunctionprocessArray(array) {
  array.forEach(async (item) => {
    await func(item);
  })
  console.log('Done!');
}

But forEachwill not wait for the completion of the task. forEach- synchronous operation. It will simply start the task and go on. Check on a simple test:


functiondelay() {
  returnnewPromise(resolve => setTimeout(resolve, 300));
}
asyncfunctiondelayedLog(item) {
  // мы можем использовать await для Promise// который возвращается из delayawait delay();
  console.log(item);
}
asyncfunctionprocessArray(array) {
  array.forEach(async (item) => {
    await delayedLog(item);
  })
  console.log('Done!');
}
processArray([1, 2, 3]);

In the console we will see:


Done!
1
2
3

In some situations this may be a normal result. But still, in most variants, this is not the appropriate logic.


2. Cycle processing sequentially


To wait for the result of the execution of the loop body, we need to return to the good old "for" loop. But this time we will use its new version with a construction for..of(Thanks to Iteration Protocol ):


asyncfunctionprocessArray(array) {
  for (const item of array) {
    await delayedLog(item);
  }
  console.log('Done!');
}

This will give us the expected result:


1
2
3
Done!

Each array element will be processed sequentially. But we can start the cycle in parallel!


3. Cycle processing in parallel


You need to slightly change the code to start operations in parallel:


asyncfunctionprocessArray(array) {
  // делаем "map" массива в промисыconst promises = array.map(delayedLog);
  // ждем когда всё промисы будут выполненыawaitPromise.all(promises);
  console.log('Done!');
}

This code can run several delayLogtasks in parallel. But be careful with large arrays. Too many tasks may be too hard for the CPU and memory.


Also, please do not confuse the "parallel tasks" from the example with real parallelism and flows. This code does not guarantee parallel execution. Everything depends on the body of the cycle (in the example it is delayedLog). Network requests, webworkers and some other tasks can be executed in parallel.


Also popular now: