WittCode💻

JavaScript Asynchronous Programming Explained

By

Learn what asynchronous programming is, why it is needed, and its specificity to JavaScript. We will also go over what synchronous programming, callback functions, event handlers, and promises are.

Table of Contents 📖

What is Asynchronous Programming?

Asynchronous programming is a programming technique that allows a program to respond to other tasks even while a long running task has been started. In other words, the program does not have to wait for a long running task to finish before doing other things. When the long running task has finished, the result is available to the program. Common examples of long running tasks are HTTP calls and database queries.

What is Synchronous Programming?

Before we dive deeper into asynchronous programming lets talk about synchronous programming. Synchronous programming is a programming technique where the program is stepped through one line at a time in the order it was written. In synchronous programming, a following line of code is not touched until the one before it is completed. The following is a synchronous program.

const name = "WittCode"; console.log(name);

Here, the log statement will not be executed until the assignment of the string WittCode to the variable name has completed. Synchronous programming can become very inefficient for tasks that require a long time to complete. The bigger the task the longer it will take to complete. If a task takes a long time to complete, the program will be completely unresponsive to anything else until the long running task has completed.

How does Asynchronous Programming Help?

Asynchronous programming solves the issue with synchronous programming by starting a long running task, returning from the task immediately so the program can continue, and then notifying the program with the result of the long running task when it completes.

Event Handlers

A common example of asynchronous programming is the use of event handlers. To better demonstrate this, lets create a button that prints Hello WittCode whenever it is clicked.

Event handlers are asynchronous as waiting for the button to be clicked is a long running task. However, the program can still run even though it is waiting for a click. When the button is clicked, the task has completed, and the event handler (the function provided to the onclick method) is notified.

Callback Functions

Event handlers are a type of callback function, a function that is passed to another function to be called when the function it was passed to is completed. Callback functions are not asynchronous by definition, but they are commonly used to respond to asynchronous operations. For example, the callback function we provided to the button onclick method was asynchronous but the callback function defined here is not.

function task1(value, callback) { console.log('Task 1 has started'); console.log(Value is ${value}); console.log('Task 1 has completed'); callback(); }

function CallbackFunction() { console.log('Callback function called'); }

task1(0, CallbackFunction);

Here, when task1 is complete, the function supplied as an argument to it is called (the callback function). This is not asynchronous code, but we can see how it works well with asynchronous operations. Specifically, the callback function is called when the function it was provided to has completed. However, things can get ugly and complicated when callbacks have callbacks which have callbacks and so on. This is referred to as the pyramid of doom. To combat the horror of nested callbacks, modern asynchronous APIs don't use callbacks but rather promises.

Promises

Promises are JavaScript's modern approach to handling asynchronous programming. A promise is an object returned by an asynchronous function that represents the asynchronous task's current state. The asynchronous task is often not completed when it is returned to the caller, instead, it has methods to handle the eventual success or failure of the asynchronous operation. To demonstrate promises, lets make an asynchronous call using the JavaScript fetch API.

const resp = fetch('https://jsonplaceholder.typicode.com/todos/1'); console.log(resp); // Promise {}

Here, we are making a GET request to jsonplaceholder, a free online REST API that provides fake data. The fetch method is built into modern browsers and returns a promise. We capture this promise inside the variable resp and log it to the console. The value it displays is a pending promise. In other words, the asynchronous operation is still ongoing. However, as the fetch method returned a promise object instantly, we can continue the program.

const resp = fetch('https://jsonplaceholder.typicode.com/todos/1'); console.log(resp); // Promise {} console.log('Program continuing!'); // Program continuing!

So even though the asynchronous task is still ongoing, the program continued to the next line and logged Program continuing!

Promise States

Promises can be in one of three states: pending, fulfilled, or rejected. Previously, we saw a promise object in the pending state. When a promise is pending, this means it has been created but the asynchronous task it represents has not completed yet. In other words, the asynchronous task has neither been fulfilled or rejected. When a promsie is fulfilled, this means that the asynchronous operation was successful. When a promise is successful, the then method is called on it.

const resp = fetch('https://jsonplaceholder.typicode.com/todos/1'); resp.then((data) => { console.log(resp); // Promise {: Response} });

The then method takes a function where the first argument is the data returned from the successful asynchronous call. If we log resp inside the then method, we can see that the state of the promise object is now fulfilled. On the other hand, if an asynchronous operation fails then its catch method is called.

const resp = fetch('https://example.com'); resp.then((data) => { console.log(resp); // Not called, there was an error }).catch((err) => { console.log(resp); // Promise {: TypeError} });

Here, our asynchronous operation is making a request to a different origin without the appropriate CORS headers set. This causes a CORS error which rejects the promise. Inside the catch method, we can see the status of the promise is rejected. Nowadays, we can use the keywords async and await to write asynchronous code that looks like synchronous code. But these are just a different way of working with promises and is beyond the scope of this article.