Asynchronous processing in JavaScript
Tags: javascript, beginners
Published: 2023-04-15 13:48:36

This is a continuation of [Introduction to node and npm](https://dev.to/adderek/intro-to-node-and-npm-3apd)

## 1. Introduction

JavaScript engines are generally single-threaded. Long call processing would normally block any other call but we leverage asynchronous processing to interrupt currently executed procedure allowing engine to switch between the jobs. Behind the scenes Input/Output is multi-threaded.
There are many ways JS developers could achieve effects similar to multi-threading.

## 2. How asynchronous processing works

You send a message (ex. a request to process a resource like "add a new user to the system") and usually get a confirmation that the request was received. In REST this might be 202 (Accepted) and "location" header, in messaging systems a valid _ID of the request_.
But the message is only queued for processing, not yet processed.

Whenever system has free resources (ex. is not doing anything more important or queued before your request) then your request could be processed. This takes usually between milliseconds and weeks. It could be for example a request to add a new bank account that must be verified by someone.

When requesting something, often a feedback (confirmation and/or return data) is expected. This is usually done using message sent by the processing system to a place you can access. Often it would contain "correlation-id" that is the _ID of the request_ to indicate it is a response to previous request. Simply saying: We cannot send response (not yet) but we can send instruction (ex. URL) to check if it was already finished.

## 3. Several attempts to use asynchronous processing

### 3.1. How the code execution gets postponed

In JavaScript calls could be postponed whenever you use a promise (or async/await which is using promises behind the scene).

```
async function log1(msg) {
  await console.log(msg);
}
async function log2(msg) {
  console.log(msg);
}
function log3(msg) {
  console.log(msg);
}
function log4(msg) {
  return new Promise(resolve=>console.log(msg));
}

setTimeout(()=>log1(1), 0);
setTimeout(()=>log2(2), 0);
setTimeout(()=>log3(3), 0);
setTimeout(()=>log4(4), 0);
```

Usually prints 1,2,3,4 as expected.
Internally those are 4 different calls scheduled to happen with 0ms delay (no delay). And the order of execution doesn't need to keep the sequence.

`log1` is async (returns a promise) and stops execution when calling `console.log` because there is `await`. Once called, the promise is not yet fulfilled - it would be after `console.log` returned,then `log1` could continue, and finally it returns. You are sure that `console.log` returns before `log1` gets fulfilled (only because there is "await").

`log2` is async (returns a promise) and triggers `console.log` and returns. You have no guarantee that `console.log` prints output before `log2` gets fulfilled.

`log3` is just a function. The `console.log` might be scheduled but `log3` returns and continues to execute your code. The JavaScript thread is blocked until it returns (though it can schedule some promises).

`log4` returns a promise. Its execution could be delayed (then you see that returned promise is "pending" until it gets "fulfilled").

Think about it thoroughly and you could also omit `setTimeout` in 3 out of 4 cases - then call is made and a "pending promise" is returned. In fact the case of `log3` is probably most dangerous.

If you are using node, then internally [libuv](https://github.com/libuv/libuv) is used for calls and also for anything like input/output (ex. to print something onto the console).

### 3.2. Calling other applications

You can sent a request to another system, service or application. You can send a REST request and should wait for the response (even "Accepted" when task is only queued). Or publish this to a queue yourself.

### 3.3. Workers

Node can run parallel instances of node called "workers". Workers can act as a parallel process to perform heavy work without blocking the main thread.

## 4. What to look at

### 4.1. Intervals

You can schedule a method to be called with a fixed interval:

```
let counter = 0;
let intervalHandle = undefined;
const intervalInMilliseconds = 10;

function myMethod() {
  counter++;
  for(let i = 0; i < 9999; i++){console.log(i);}

  if(counter > 10) {
    clearInterval(intervalHandle);
    intervalHandle = null;
  }
}

intervalHandle = setInterval(myMethod, intervalInMilliseconds);
```

You could expect that it prints number from 0 to 9999 every 10ms until 10 iterations are made within 100ms.
The issue is that you have no guarantee that methods would be called every 10ms. This could lead to 2 problems:
- Your method could be called more than once at the same time (if it is using asynchronous processing internally)
- Your method could be executed with longer interval (it needs to print the numbers first)

A bit corrected code:

```
let counter = 0;
const minimumIntervalInMilliseconds = 10;
function myMethod() {
  counter++;
  for(let i = 0; i < 9999; i++){console.log(i);}

  if(counter <= 10) {
    setTimeout(myMethod, minimumIntervalInMilliseconds);
  }
}

setTimeout(myMethod, minimumIntervalInMilliseconds);
```

## 4. Dangers of single-threaded app

If your application runs inside a docker container that has a healthcheck and restarts whenever healthcheck is not responded (ex. timeout 1s, interval 1s), then any job blocking main thread for 1000ms would result in container being restarted.

If your app runs on kubernetes and your is blocked over the timeout threshold then your application might be "disconnected" (readiness probe) or restarted (liveness probe).

If you are connected to a queue (ex. kafka or RabbitMQ) and your application is busy for the timeout (ex. 3000ms for kafka subscriber) then your application will be kicked-out and considered "dead". I have seen this scenario where developers tried to update RabbitMQ and its libraries searchign for an issue in the server - where the cause was in the node code.