JavaScript async call analysis
Tags: javascript, async, webdev
Published: 2024-02-12 11:16:49

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

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

```
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**
```javascript
(async () => {
   console.time('async-await');
   for ( let i=0; i<n; i++ ) {
      await async_func();  
   }
   console.timeEnd('async-await');
})();
```

And time spend

```
    n     #    times
------------------------------------
   10     #    5.10s 
------------------------------------
   100    #    50.994s
------------------------------------
  1000    #    8:28.113 (m:ss.mmm)
------------------------------------
```

**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**
```javascript
(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');
})();
```
**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**
```javascript
(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');
})();
```

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)
------------------------------------
```
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
```javascript
[
   [1,2,3,4], // 0-group
   [5,6,7,8], // 1-index
   [9,10]     // 2-index
```
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)
------------------------------------
```

And what if we don't need sequential execution, let's try to run in parallel
```javascript
(async () => {
	console.time('promise-parallel');
	await Promise.all(
    Array.from({ length: n}).map( (i) => async_func())
  );
	console.timeEnd('promise-parallel');
})();

```
and time execution
```
     n     #    times
------------------------------------
     10    #    502.277ms
------------------------------------
    100    #    504.385ms
------------------------------------
   1,000   #    514.173ms
------------------------------------
  10,000   #    547.021ms
------------------------------------
  100,000  #    3.649s
------------------------------------
```
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>)
        .................
        .................
```