JavaScript Essentials has collection of topics that will introduce you to the essential concepts and constructs of the language. The topics are covered in no particular order and you can start anywhere as per your comfort.

A Bit of Context behind the Idea

Let’s start by stating that JavaScript is single threaded (we will talk about this in detail in another article). This means only one thing can execute at a given time and all other things wait for their turn, which can get pretty scary. Thankfully, most of our use-case results in code that executes later or when an event occurs. These are non-blocking tasks which are going to complete asynchronously.

Callbacks and Asynchronicity

Asynchronous code completes after a time period, which is not known to us. A simple way to “wait” for an asynchronous function to return a result is to pass a callback function to it.

A callback function is a function that is passed to another function as a parameter, and the callback function is called inside the other function.

For example:

In the above code example, we are calling getUser() method which will fetch the user by making an API call. What we are saying here is: “Hey `getUser`, go get a user for me with user-id and take your time. Once you have it, run this function that I passed you with the details of the user or error in any case”.

If we think about it, it doesn’t really matter to us how much time this asynchronous code takes to execute, if it’s going to give us the result immediately or if it’s going to take a while to complete the operations before giving the result. So, by passing a callback we know that whenever the result comes, my code would be executed.

Fallbacks with Callbacks

Well, we are handling asynchronicity of our code by using callbacks but they have some problems associated with them.

Inversion of Control

When we use code that requires callback, we are essentially inverting control of our program. We are expecting that the function receiving our callback will execute it at the right time and pass us the required results. In other words, we are placing lot of trust on the function that receives our callback. This is okay if the asynchronous code requiring a callback is written by us, but not when we are using a third-party library.

A third-party library invoking your callback.

When we implicitly trust any piece of code consuming our callbacks, we expect them to:

Call the callback at the right time Call it only once Succeeds or fails when its meant to Sends us the correct arguments and more…

Handling Multiple Asynchronous Tasks

Say we want to execute multiple asynchronous tasks together and do something when all of them completes. With callbacks, it isn’t really reasonable or understandable. Let’s take a look at an example:

Though there are many other ways to handle this with callbacks but trust me it gets clumsier and harder to reason about and understand.

Callback Hell

As a human being, our brains naturally plan events to happen sequentially (one thing will happen, then another, then another). The only way to plan things sequentially using callbacks is to nest them inside each other.

Let’s consider a case where we have multiple asynchronous tasks and all of those are dependent on one another. So, for three dependent tasks, third tasks is dependent on result of second and second is dependent of first. Let’s take a look at the piece of code that is implemented using callbacks:

Looking at the above code, our inner engineer wakes up saying: “Wait! there might be a way for us to avoid this nesting of callback and still achieve the same results”. Yes, most certainly we can! Take a look at the following code:

We have tackled one problem with this code: we successfully avoided callback hell. We will take a look at how we can improve it further.

Implicit Error Handling

We’ve seen in above code examples how we are handling the errors in callbacks. We are using Node’s style of error handling called error first callbacks. Then there is also a style where you pass different handlers for success and errors (this again adds up to our inversion of control problem).

Though this looks decent and it is, but propagation of the error in large code-base becomes quite tricky and less understandable.

Introducing Promises

Well, your birthday is coming up and your dad promises you that he will get you a new bike on your birthday. You don’t know if you’ll get your bike until your birthday. Your dad might or might not buy you a new bike depending upon situations. But in the end, you’ll either get a new bike or you’ll know that you won’t be getting it because of such and such reason.

A Promise represents a value that may not be available yet, but will be either resolved or a reason that it’s not resolved, at some point in future. It allows you to write asynchronous code in more synchronous way.

Life Cycle of a Promise

A Promise can be:

Fulfilled — The task related to the promise succeeded

Rejected — The task related to the promise failed

Pending — The task is still in progress and the promise hasn’t fulfilled or rejected yet

Settled — The task related to the promise has either succeeded or failed

Let’s say we are using a promise to make an API request. Since there will be a delay in getting the response from the server (network latency, server processing, etc.,), the promise won’t contain the final value immediately nor it will be able to report an error. This is when a promise is said to be pending.

Once the server responds back, there can be two possible outcomes:

The Promise will get the value that is expected from the API and will become fulfilled The API call me error out due to some circumstances and the Promise might get rejected

Once a promise is fulfilled or rejected, it’s known to be settled. Once settled (fulfilled or rejected), the promise can not transition into any other state which makes it more or less like a contract on which we can rely on.

Promise Lifecycle

The Promise API

We will see how to create a promise, get a value out of the promise, handle error cases and other situations.

Create a Promise

Let’s see how we can create our own promise:

In the above code, we have wrapped our usual XMLHttpRequest inside our promise definition. If our request errors out due to some reason, we reject the promise. If we receive a response from the API and we receive an error status, we reject the promise. If everything works out fine, we resolve the promise with the value that we received from the API.

Consuming our Promise

Since we have create our promise, let see how we can use that.

From our ‘creating a promise’ example, we know that our get function returns a promise. To take an action on this promise when it’s resolved or rejected, we use then and catch to handle those.

then accepts two arguments, both of which are optional. A callback for a success case, and another for the failure case. That’s one way you can handle the success and failure cases. You can also use then for the success case and catch for failure.

Dealing with Multiple Promises

We have two different API’s for this and both function a bit different from one another. Promise.all() when we want all of our promises to complete and then do something, Promise.race() when we want any of the promises to complete first and do something.

Isn’t the above code is simple and understandable? Imagine doing this in callbacks without creating a mess.

Chaining Promises

Well, I remembered your dad promised you a bike on your birthday. Let’s say you further promised your girlfriend, you’ll take her out to a nice place on your bike on your birthday. This is another promise. Your trip with your girlfriend can only happen when you get your bike.

Shall we write a code for this? I think we should:

then isn’t the end of the world. You can chain multiple then together to transform values returned by previous promise or to run additional asynchronous actions one after another.

Transforming Values

Let’s see how we can utilise this in our benefit:

The response received in first then was a serialised string and we converted that to json value and returned.

Queuing Multiple Asynchronous Tasks

Chaining multiple then is somewhat tricky behind the scenes. If you return a value, it will be passed to next then , but if you return a promise, the next then waits for the value and is only called when the promise settles.

Error Handling

Do remember promise have both success and an error handler. Here is an example:

What if the success handler fails? There is nothing that can catch that exception. This means that the error in your app is not handled. For this reason people consider this as an anti pattern.

We can achieve the same thing using catch which is a bit neater and will not let the error get away in your complete flow:

Any error occurs in entire flow of this, catch will be executed. The difference is very little but has a big impact.

In the end, handling the error cases with catch is just better.

Compatibility Across Platform

Promises have arrived in majority of browsers and JavaScript platforms. Here is the compatibility chart taken from Mozilla MDN:

Platform compatibility of Promises natively

The above image is a screenshot of the compatibility chart taken at the time of writing this article which might get outdated. Do check Mozilla MDN’s Promise documentation for the latest compatibility chart.

Addressing Callback’s downsides with Promises

We talked about certain downsides of callbacks earlier. Lets talk about how promises solve those.

Inversion of Control

We know that Promise serves as a container for a future value and follows sort of a contract in the way it executes and transitions it’s state. We know that a promise once settled can not transition to any other state, which means that resolve or reject can only have an effect once no matter how many times they have been called.

Furthermore, by using promises, we are retaining the flow of control rather than giving the control away. We expect the asynchronous calls expecting a callback to give us handlers called .then() and .catch() .

At the end, we can expect certain kind of behaviour from promises which are strict as a contract. To know more about this, read the Promise/A+ spec.

Handling Multiple Asynchronous Tasks

We already saw how we handle multiple asynchronous tasks via promises. Let’s compare both callbacks and promises way of doing things.

Multiple asynchronous tasks execution was very understandable which in the end makes your code more maintainable and scalable which was very hard with callbacks. Also, we didn’t need some set of counters to check if all of our asynchronous tasks were executed or not with promises. It made sense executing multiple asynchronous tasks with promises.

Callback Hell

We saw how callback hell were created, when we had multiple dependent asynchronous tasks. We also saw how we handled such callback hell with callbacks itself. Callback hell generally happens when we are doing multiple asynchronous task one after another in series. We already saw how multiple dependent tasks are handled in promises.

Error Handling

We saw how promises make error handling so much better and you have full control of handling it. Handling errors specific to a callback and propagating it in a large multiple asynchronous flow is hard and clumsy. Also, it takes away your flow of control. Doing so in promise will let you keep the control of handling the error and is easier to propagate though your application logic.

Wrapping Up

Asynchronous programming is hard to get your head around but once you do, it becomes your habit and you feel pretty comfortable with it. We have seen how callbacks can be used to write asynchronous code, and what their drawbacks are. We then learned about promises and saw how it made working with asynchronous code easier. In general, promises are a very good tool to handle asynchronous tasks because of it’s strict specification of implementation and improved readability.

For diving further into asynchronous programming read this chapter of You Don’t Know JS and also this course on Rethinking Asynchronous JavaScript.

I would also recommend this article from Jake Archibald and this course on Udacity about Promises by Google.

Hopefully, this article scattered some light upon the basic working of Promises and why do we use them. Will catch you in the next article where we will explore some more essential concepts of the language.

Let me know in the comments below, your point of view on promises, correct me if I am wrong anywhere or feel free to add any points that I missed.

Thank you.