DEV Community

Sabbir Hossain
Sabbir Hossain

Posted on

JavaScript async call analysis

let's say we have a async function like following:

let counter = 1;
async function async_func() {
    return new Promise((resolve, reject) => { 
      setTimeout(function() {
        resolve(counter++);
      }, 500); 
  })
}

Enter fullscreen mode Exit fullscreen mode

It's only task to wait 500ms and then return latest counter.

Let's call this async_func function using async-await and see how much time it takes for different range.

Sample 1

(async () => {
   console.time('async-await');
   for ( let i=0; i<n; i++ ) {
      await async_func();  
   }
   console.timeEnd('async-await');
})();
Enter fullscreen mode Exit fullscreen mode

And time spend

    n     #    times
------------------------------------
   10     #    5.10s 
------------------------------------
   100    #    50.994s
------------------------------------
  1000    #    8:28.113 (m:ss.mmm)
------------------------------------
Enter fullscreen mode Exit fullscreen mode

Sample 1's code run sequentially. This will be use for absolute sequence. Although there is another way (given below) which will work in the same manner without any async-await. As you can see, for 1000 call takes 8:28.113 (m:ss.mmm) which is way too much (this test run on local, it can be changed based on computer power).

Sample 2

(async () => {
  let finalResult = [];
  console.time('reduce-sync');
  await Array.from({ length: n }).reduce((promise, dt) => {
    return promise.then(
      () => 
        async_func()
          .then(result => finalResult.push(result))
      );
  }, Promise.resolve());
  console.timeEnd('reduce-sync');
})();
Enter fullscreen mode Exit fullscreen mode

Sample 2's code also run sequentially and performance is almost same. What we are doing here?
array.reduce((promise, dt) => {}, Promise.resolve()), here accumulator is promise, so we can use promise.then(....)

Let's try to improve performance with sacrificing absolute sequential execution.

Sample 3

(async () => {
  console.time('reduce-semi-sequential');

  const oldData = Array.from({ length: n });
  const newData = [];
  while (oldData.length) newData.push(oldData.splice(0, 4));

  let finalResult = [];

  await newData.reduce((promise, currentList) => {
    return promise.then(() =>
      Promise.all(
        currentList.map((current) => async_func(),
        ),
      ).then((result) => {
        finalResult = [...finalResult, ...result];
        return Promise.resolve();
      }),
    );
  }, Promise.resolve());

  console.timeEnd('reduce-semi-sequential');
})();
Enter fullscreen mode Exit fullscreen mode

And time spend

    n     #    times
------------------------------------
   10     #    1.527s 
------------------------------------
   100    #    12.757s
------------------------------------
  1000    #    2:07.140 (m:ss.mmm)
------------------------------------
  10000   #    21:10.670 (m:ss.mmm)
------------------------------------
Enter fullscreen mode Exit fullscreen mode

as we can see, performance is much improved. Here first we are change 1-D
[1,2,3,4,5,6,7,8,9,10] array to 2-D array

[
   [1,2,3,4], // 0-group
   [5,6,7,8], // 1-index
   [9,10]     // 2-index
Enter fullscreen mode Exit fullscreen mode

now every 0/1/2-group will run sequentially, inside each group will run in parallel.
It could be improve if we increase newData.push(oldData.splice(0, 4) total array size from 4 to higher. For example, by changing newData.push(oldData.splice(0, 4) to newData.push(oldData.splice(0, 10), I get following result

     n     #    times
------------------------------------
   1,000   #    5.126s
------------------------------------
  10,000   #    51.081s
------------------------------------
  100,000  #    8:29.471 (m:ss.mmm)
------------------------------------
1,000,000  #  1:26:01.277 (h:mm:ss.mmm)
------------------------------------
Enter fullscreen mode Exit fullscreen mode

And what if we don't need sequential execution, let's try to run in parallel

(async () => {
    console.time('promise-parallel');
    await Promise.all(
    Array.from({ length: n}).map( (i) => async_func())
  );
    console.timeEnd('promise-parallel');
})();

Enter fullscreen mode Exit fullscreen mode

and time execution

     n     #    times
------------------------------------
     10    #    502.277ms
------------------------------------
    100    #    504.385ms
------------------------------------
   1,000   #    514.173ms
------------------------------------
  10,000   #    547.021ms
------------------------------------
  100,000  #    3.649s
------------------------------------
Enter fullscreen mode Exit fullscreen mode

as you see, run in parallel improve code performance a lot. But there is some issues also. For example, if array size is very high like 1M (depends of CPU), then we will get RangeError like following

RangeError: Too many elements passed to Promise.all
        at Function.allSettled (<anonymous>)
        .................
        .................
Enter fullscreen mode Exit fullscreen mode

Top comments (0)