JavaScript Promises, road to async/await
Introduction
Asynchronous and synchronous are kinda complex concepts for beginners. Even some experienced developers fail to comprehend the differences.
The synchronous code is something that executes sequentially and everything else waits until that piece of code executes. When you do something asynchronously in JavaScript, you are scheduling it to execute later. And later is some time in future after all synchronous code completes. Actually, that is only the earliest possible time that it can execute. It can happen even later if there is another async code that is scheduled to execute.
Why do we need the asynchronous code? We use it to create a non-blocking JavaScript code. That is code that we will not prevent code after it to execute. On the frontend side, it is code that will not block the UI and keep the UI responsive to the user. In Node world, blocking methods are the ones that execute synchronously and non-blocking ones are those that execute asynchronously. Node’s heavy usage of async code helps it to stay fast and responsive.
Promises are directly related to asynchronous code. More about that later, let us first see how we can actually create code that is asynchronous.
How to do we create async code?
There are several built-in things that you can use with JavaScript to create async code.
XMLHttpRequest or XHR is a well-known way to interact with servers – send and receive data. We can use it to load data from the server without having to do a reload on the page. XMLHttpRequest actually has both sync and async version, tho sync one is almost always avoided.
fetch is a modern replacement for XMLHttpRequest and it is based on Promises.
Here are commonly used ways to create async code in JavaScript:
- setInterval
- setTimeout
- XMLHttpRequest
- fetch
- WebSocket
- Worker
An example with setTimeout:
This will print out the following:
“A very long for-loop incoming..”
forLoopTimer: 847.394287109375ms
“After the for-loop”
“inside of setTimeout()”
Even though for loop lasted quite long (on my machine it was about 847 ms), the code inside of setTimeout, the async part of code still ran after that. And that is how it goes in JavaScript, you will always get the same result, the async code will run after sync code finishes. That means, that all synchronous code will first execute and then any async code will start executing.
I will not get into the nature of event loop and how JavaScript handles the execution. However, I can recommend you a great talk which is tightly related to this – Philip Roberts: What the heck is the event loop anyway? | JSConf EU 2014.
Promises vs callbacks
Promises are usually used as an alternative to callbacks for getting results of asynchronous code. Why you should prefer Promises over callbacks is another topic. However, you can try to read a chapter related to Promises, from an amazing book – Exploring ES6.
Some of the advantages of Promises over callbacks:
- Promise-based functions return results, they do not directly continue and control execution via callbacks.
- Chaining is simpler due to .then() method that every Promise contains, you can simply chain result of one Promise to another.
- Error handling is simpler with Promises, because, once again, there isn’t an inversion of control. Furthermore, both exceptions and asynchronous errors are managed the same way.
- With callbacks, the parameters of a function are mixed; some are input for the function, others are responsible for delivering its output. With Promises, function signatures become cleaner; all parameters are input.
In this post, we will see how you can use Promises and how you can wrap your existing code with Promise. After that, we will see how we can use the power of async and await keywords to write asynchronous code in a synchronous manner.
Promises
Promises return results asynchronously. When we create a new Promise we provide a function to the constructor. The function will contain two parameters. Both parameters are actually functions, one is for success (resolve) and another one for error (reject).
Wrapping callbacks
Let’s say we have a function that’s using some 3rd party API to send email messages. This API is using callback style with two callback functions.
emailClient.send( { params }, function onSuccess(result) { console.log('Sent!'); }, function onError(error) { console.error('Error', error); } );
We can easily wrap this with a Promise. We will create a new function that will send an email. We just need to return a Promise and place our old code inside of a function that we pass to Promise constructor. After, we call resolve and reject functions at appropriate places.
function sendEmail(params){ return new Promise(function (resolve, reject) { mandrillClient.messages.send( { params }, function onSuccess(result) { console.log('Sent!'); resolve(result); }, function onError(error) { console.error('Error', error); reject(error); } ); }); }
async and await
Here is a brief info from MDN:
When an async function is called, it returns a Promise. When the async function returns a value, the Promise will be resolved with the returned value. When the async function throws an exception or some value, the Promise will be rejected with the thrown value.
An async function can contain an await expression, that pauses the execution of the async function and waits for the passed Promise resolution, and then resumes the async function’s execution and returns the resolved value.
The purpose of async/await functions is to simplify the behavior of using promises synchronously and to perform some behavior on a group of Promises. Just as Promises are similar to structured callbacks, async/await is similar to combining generators and promises.
For us to be able to use async/await pattern, we need to declare a function as async. Once we do that, we can use await keyword inside of that function. We usually await Promises, so let’s see how we can await a Promise that we created previously:
The end result is pure awesomeness: we managed to write async code in a synchronous manner. We can simply use await and no need for then or any kind of callback functions.
async / await – example with fetch
Let’s write a code that does fetching of a single post from some fake API, and the code will return its value via Promise.
Notice the mainFunction and how we need to nest then inside of another then.
Compare that to the version with async/await where we only have two awaits instead:
Much cleaner! Imagine if we had five functions that required a result from previous one. The first version with nesting then methods would be really messy and indentation would be too much to handle :).
With async/await you would have five lines that looked like synchronous code and you would also be able to use one catch block if it’s possible.
Handling errors
And how do we handle errors? That is one of the awesome things about async/await, we handle them in the same way that we handle synchronous.
You probably already noticed that but we only need to use try/catch block and if an error occurred inside of Promise it will get in the catch block.
Using same logic we can await the sendEmail function:
Chaining async functions
Once you mark a function as async and you want to call it from somewhere else and wait for its result you also need to mark the callee function as async and await the function that is being called.
If you have let’s say a function named functionA that returns a Promise, and you have another function named functionB that is marked as async and it awaits a Promise from functionA and you want to call functionB from some other function called functionC then you also need to mark functionC as async.
An example:
Basically, if you need to use await keyword you need to mark the function as async.
If you have one async function that awaits for some Promise and you have chain of functions that end up calling that function, all functions in that chain need to be marked as async and use await to wait for next function in the chain.
Summary
You should use Promises because they are standardised in JavaScript. Before that happened we had callbacks, XHR, IndexedDb and few other ways of handling async computations. With ES6/ES2015, we got a standardised way of handling async results – Promises/A+ and most of new APIs in libraries are based on Promises.
You should also use async and await whenever possible, to simplify your code. You will prevent code nesting and also be able to write async code in a synchronous manner and handle errors via try/catch blocks.