Asynchronous programming is a concept that causes confusion and frustration for many new developers. However, asynchronous programming is incredibly useful and important to know as it greatly improves the performance and responsiveness of an application. Given that a great user experience is essential in the success of an application, every developer should have the ability and skill to use asynchronous code where appropriate.
Synchronous vs. Asynchronous Code
To begin with, let’s go through the difference between synchronous and asynchronous code.
While this normally works fine, we can run into problems when a certain unit of code takes a long time to execute, which then holds up the rest of the program. When we have a hold up in the execution of our code, this results in a bad user experience — we all know the exasperation and frustration that’s felt when a program seemingly freezes for minutes at a time, with no end in sight. This might happen when we have to make a fetch request to the server for a large resource or when we make calls to an expansive database.
One method of working around this is to employ asynchronous programming, which allows our program to do more than one thing at a time. This method of programming allows us to do things concurrently in a non-blocking way. In this paradigm, our program allows for a unit of code to run simultaneously and separately from the rest of the application; when that unit of code has completed (or failed), the main thread will be notified. The rest of the program is not blocked from running while we wait for the task to finish, and thus the user does not experience a frustrating freeze in the program. This ultimately creates a better user experience and allows for an increase in the overall efficiency of the program.
Employing Asynchronous Code
Now that we have a clearer idea of what asynchronous code is, the question that follows is: how do we start implementing asynchronous code?
Asynchronous programming is typically implemented via callback functions and promises.
Let’s take a look at the setTimeout() function. This (similar to the setInterval() function) is a function of the DOM Window object, and it tells the program to execute a block of code after a specified amount of time has transpired. This function takes some parameters: a callback function and a number that represents the time in milliseconds that the program should wait before the code is executed. Optionally, you can also pass in values that serve as parameters to the callback function when it is run.
Here, the callback function still runs on the main thread, but only after the specified time interval. So in the above example, after 3 seconds pass, the alert that has popped up will be removed.
Most asynchronous code that you work with will come from a library or an API. Examples of this include Fetch, XMLHttpRequest, jQuery Ajax, and Axios, just to name a few.
Let’s now explore an example using XMLHttpRequest, an API that allows us to make asynchronous requests for data from a server in the background. XMLHttpRequest is useful because it allows us to do things such as retrieve data from the server without having to refresh the page or update a specific part of the application without stopping the whole program.
Below is an example of an XMLHttpRequest:
Notice that in the above code, we are able to do one thing if the XMLHttpRequest is successful and also specify what should happen should we not get a success status.
While the above examples are relatively straight forward, we can run into problems involving callback functions in more complex examples where there aren’t specified intervals of time or where there are nested callback functions. Let’s imagine (as will often be the case) that we have a program where there is a unit of asynchronous code, and within this unit, there are nested callback functions. We don’t always know exactly how long a function will take to run and complete, or when something will fail; this can result in a massive mess that becomes hard to read and debug.
Promises were introduced as a way to deal with what developers call “callback hell”. Promises allow us to implement asynchronous code in a way that is cleaner and a little easier to understand.
According to MDN,
Promiseis a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.
An example of promises is fetch(), which is another API that works similarly to the XMLHttpRequest API, except that it returns promises, and is thus more efficient.
Below is a very simple example of promises in action using the fetch API:
We are making a fetch request to a specified API base, and then when we get a response, we do something with that response. In this case, we turn the response into a JSON object and then when that happens, we then console log the data.
Doesn’t that look a little bit neater and easier to understand?
Now let’s take it one step further. While callbacks and promises work, they can easily become convoluted and hard to read. Async/await is a newer and more reliable way of dealing with asynchronous functions. It gives us a way to write asynchronous code in a way that looks like synchronous code, which allows for better readability.
While I am not going to dive deep into async/await functions, the main gist of it is that async/await allows us to deal with promises in a way that is much more intuitive. We can write an async function by simply using the async keyword — this says that the function will always return a promise. Await functions work in tandem with async functions. Await functions are used only within async functions and it tells the program to wait until the promise returned by the await function is resolved, and then actually returns it.
To wrap up, asynchronous functions can be an incredibly useful tool in a developer’s toolbox. Asynchronous code allows for enhanced performance and a better user experience, resulting in an overall better application. The trick is knowing when to use asynchronous code — practice makes perfect!