All you need to know about asynchronous JavaScript
Tags: webdev, javascript, beginners, tutorial
Published: 2023-02-23 17:10:19

If you have ever spent time in the JavaScript world you will probably have heard the phrase "JavaScript is a single threaded language". But what does that mean exactly? Put simply, it means that JavaScript executes code line by line in sequential order, and after each line has been executed there's not going back to it.

### So what's the problem?
Imagine a situation where a user wants to display a list of comments on a website. They click a button to view the comments. However, while the comments are being fetched from a server (and let's say that the fetching takes a while), the user cannot interact with anything else on the website because JavaScript is busy executing the show comments functionality. In other words, all other code is **blocked** until JavaScript has completed this comment fetching task. Not very user friendly.

```javascript
const name = ‘Pippa’;
const number = 100_000_000;
for (let i = 0; i <= number; i++) {
  console.log(i)	
}

console.log(`Hi, my name is ${name}`)
``` 
In this trivial example, a time consuming command (in this case, iterating through a large for loop) would block the final `console.log()` command for an undetermined amount of time. Nothing else can happen in the program while the for loop is running, and who knows how long that could take to complete.

#### The solution: enter asynchronous JavaScript.
Async JS is a technique (or rather, group of techniques) which enables your program to continue executing code while time consuming tasks are deferred. No, it's not magic, it is just a set of features of both JavaScript and the browser which when combined allow us to write **non-blocking code**. Let's start with the first, and probably most simple, method.


### Callbacks: using the `setTimeout()` API
If you have read up on the JavaScript runtime environment (and if you've haven't you can [do so here](https://dev.to/ppiippaa/javascript-runtime-explained-all-you-need-to-know-about-client-side-js-code-execution-5g8e)) this one should come as no surprise. The browser provides the event loop, the callback queue and the microtask queue. These features allow certain functionality to be deferred until all global code has been executed. `setTimeout()` (a browser API) takes a callback function and a time argument (**minimum time** in milliseconds to pass before invoking the callback) and pushes the callback function to the callback queue. The callback queue must wait for all other global code to run before anything in it can be pushed to the call stack (this is handled by the event loop), thus we can essentially call this function knowing that our global code will continue executing in the meantime. 

``` javascript
console.log('first hello');

function fetchData() {
  setTimeout(() => {
    // functionality to make XHR request to fetch data from a server and log it to the console
  }, 1000)
}

fetchData();

console.log('second hello ')

// OUTPUT --> 
// 'first hello '
// 'second hello'
// '[ object Response ] {...}'
 ```

In the above example we start with `console.log('first hello')`. We then declare the function `fetchData()`. This function emulates making an XML/HTTP request, which is wrapped in the `setTimeout()` browser API. Once we call the `fetchData()` function, the `setTimeout()` starts a timer up in the browser with a time of 1000ms. The callback function wrapped inside the `setTimeout()` is sent to the callback queue where it will be waiting for *at least 1000ms*. Meanwhile JavaScript continues on to the second `console.log('second hello')`. Once the second `console.log()` is logged to the console and the callstack is clear of all global code, the `fetchData()` callback function is able to give us the response it received from the server. No code has been blocked and our data has been fetched! 

### Promises and the `fetch()` API
*In this section we will look at promises in the context of using the `fetch()` API. It’s worth noting, however, this is not the only use case for promises. There's loads of great resources on promises out there such as [this article](https://javascript.info/promise-basics).*

With ES6 came the introduction of promises. In order to understand how they facilitate asynchronous code we need to take a closer look at their features and behaviour. Promises are just special objects that return a placeholder object until some asynchronous code has been resolved. Every promise has several properties including state, value, onFulfilled and onRejected. 
+ **State:** represents current status of the promise object which can be one of three options: **pending** (the default), **resolved** or **rejected.**
+ **Value:** this is undefined by default, but will eventually evaluate to the response of the asynchronous task.
+ **onFulfilled:** an array containing functions to be run once the value property has been successfully resolved. We push functions into this array by using the `.then()` method. The callbacks in this array receive an argument which is the response of the `fetch()` call they are chained on to.
+ **onRejected:** an array containing functions to run if the promise is rejected, so essentially error handling functions. We push functions into this array by using the `.catch()` method. The callbacks in this array receive an argument which is the error received in the `fetch()` call they are chained on to.

Promises allowed for the creation of the modern XHR browser function known as `fetch()`. I would like to stress that that `fetch()` is a **a feature of the browser**, so it is not native to JavaScript even though it looks like a regular function. Once invoked, fetch has two main tasks; one is to return a placeholder object (a promise) and the other is to send an XML/HTTP request from the browser to a specified url.

Here’s the big reveal - any promise deferred functionality is sent to the microtask queue (separate from the callback queue, which has lower priority). The functionality in the microtask queue must wait until all global code has been executed until it can be pushed to the call stack. Thus, we can:
+ Run an asynchronous operation, i.e. fetching data from a server.
+ Execute all other global code meanwhile.
+ Trigger response related functionality from the onFulfilled array once all global code has been run and we’ve received a response from an asynchronous operation. Alternatively, if the promise rejects we can trigger error handling functionality from the onRejected array.

```javascript 

console.log('me first');

function fetchData() {
  fetch('https://dog.ceo/api/breeds/image/random')
    .then(res => res.json())
    .then(data => console.log(data))
    .catch(error => console.log(error))
}

fetchData();

console.log('me second')

// OUTPUT --> 
// 'me first'
// 'me second'
// '[ object Object ] {...}'

```

And there you have it, asynchronous code which allows a more time intensive command to be run (in this case, the XHR functionality) without blocking the rest of the JavaScript code in the file.

**Side note**: there is also another promise array, similar to onFulfilled/onRejected, which allows us to run callbacks once the promise has been settled (either resolved or rejected). We push callbacks into this array using the `.finally()` method and it's useful for performing any clean up tasks.  

### Generator Functions and Async/Await
Generator functions are an ES6 feature of JavaScript. Up until now, we understand that once a line of JavaScript code has been executed it is gone for good - there’s no going back up to that line. However, the introduction of generator functions has changed this model. Generators are special types of functions marked with an asterisk. They use a very powerful keyword, `yield`. I like to think of the `yield` keyword as the `return` keyword with superpowers. `Yield` allows us to pause a generator function’s execution context, return out a value, but later re-enter that same function execution context! We re-enter the generator funtion's execution context by accessing the `.next()` method on the generator function. This can be quite tricky to wrap your head around, so let me show you an example:

 ```javascript

function* myNumberGenerator() {
		yield 1
		yield 2
		yield 3
}

const returnNextNumber = myNumberGenerator();
	
const number1 = returnNextNumber.next().value; 
const number2 = returnNextNumber.next().value; 
const number3 = returnNextNumber.next().value;

console.log(number1);
console.log(number2);
console.log(number3);

// OUTPUT -->
// 1
// 2
// 3

```

On line 9 we are assigning the value of `number1` to `returnNextNumber.next().value`. We established that `.next()` allows us to re-enter the function execution context, but we have not yet seen the `.value` syntax. 
When we are kicked out of the generator function by the `yield` keyword, we know that we also receive a return value. However, what we actually get is an object with two properties; done (a boolean) and the value itself (in this case a number). In order to access just the value itself, we must use `.value`. 
On line 10, we assign the variable `number2` to the result of calling `returnNextNumber.next().value`, so `number2` evaluates to 2. This is because we re-entered the generator function where we left off and once more encounter the `yield` keyword, this time with the value 2. The same pattern continues on line 11.
  
We know that once a normal function has been invoked, its execution context is automatically garbage collected, but here we are able to go back into the function and continue running it later on in the code. Cool, right? Let’s take a look at another example:

```javascript

function* aGeneratorFunc() {
	const number = 10;
	const newNumber = yield number;
	yield 10 + newNumber;
	yield 20;
}

const returnNextElement = aGeneratorFunc();
const element1 = returnNextElement.next().value; // 10
const element2 = returnNextElement.next(2).value; // 12

// OUTPUT --> 
// 10
// 12

```

Wait, what… the number 12 was returned and not 20? Why? Because not only can we re-enter a function, we can re-enter the function and bring some new data with us. 

The first time we call `returnNextElement.next().value` on line 9, `yield number` (line 3) threw us out of the function **before** it had time to assign the value of `number` to the variable `newNumber`. So, at this point `newNumber` is undefined. The next time we call `returnNextElement.next(2).value` (line 10) with 2 as an argument, we re-enter the function where we left off last time, i.e. on line 3 where we were just about to assign the value of `newNumber`. The argument we passed in, the number 2, is inserted where we left off, so `newNumber` evaluates to 2. We move to the next line `yield 10 + newNumber`, and we are thrown out of the function execution context along with the evaluation of `10 + newNumber`, which as we now know is 2. Hence, we return out 12. If we were to re-enter the function once more and save `returnNextElement.next().value` to the variable element3, the value of element3 would be 20. 

So now we understand how it’s possible to re-enter an execution context of a function, with additional data if we wish, **but how does this link to async JS?** 

#### Introducing the ES7 feature async/await. 
Async/Await is made possible due to generators (and promises). With this ability to re-enter a function later on in our code, we can start executing a more time consuming command (such as fetching data), continue on with the global code, and then re-enter the data-fetching function once the requested data has been returned and all global code has been run. 

Async/await has a clean and simple syntax which requires you to place the async keyword before the function definition. The await keyword is used within the function scope, and commands JavaScript to pause the function execution until the result of the promise has been returned and continue on with the rest of the program code. After the global code has been run, JavaScript will return back into the function where it left off, by this time with a received response, and continue with the rest of the logic. Again, let’s take a look at an example:

```javascript

async function getData() {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
  const data = await response.json();
  console.log(data);
}

getData();

console.log('me first!');

// OUTPUT →
// 'me first!'
// [ object Promise ] {...}

```
Let’s break it down:

+ We declare an async function using the async keyword.
+ We use the await keyword to signal that a promise will be returned from the fetch call which enables us to break out of the function and return to it once the promise has been settled and global code has been run.
+ We call the `getData()` function, but the `console.log()` runs first because the fetching of API data has been deferred to the microtask queue. 
+ JavaScript continues running the global code, then re-enters the `getData()` function once all global code has run and the response is available in `getData()`. It then continues executing the rest of the `getData()` function. 

**A note on error handling in async/await.** In order to facilitate error handling in an async function, you can wrap the functionality in a try/catch statement. This gives you the option to run some code upon promise failure, essentially the same as using the `.catch()` method with `fetch()`. Using the same example we just saw, let’s see what it looks like using a try/catch statement:

```javascript

async function getData(){
  
  try {
        const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
        const data = await response.json();
        console.log(data);
       } catch (error) {
        console.log(error)
    }
}

getData();

console.log('me first');


// OUTPUT→
// 'me first!'
// [ object Promise ] {...}

// if an error occurs when fetching the data, the output would be:
// ‘me first!’
// [ object Error ] {...}

```

And there you have it, asynchronous JavaScript. If you made it this far, thanks for reading! If you want to dive deeper into the topic there are loads of great resources out there, but I thoroughly recommend watching Will Sentence’s Hard Parts video courses available on Front End Masters.