Handling “A”-synchronousity in JS

Image for post
Image for post

📄 Table of Contents

History

For the longest of time JavaScript developers had to rely on callbacks for working with asynchronous code. As a result, many of us have experienced callback hell and the horror one goes through when faced with functions looking like this.

Thankfully, then (or should we say .then()) came Promises. They offered a much more organised alternative to callbacks and most of the community quickly moved on to using them instead.

Now, with the most recent addition of Async/Await, writing JavaScript code is about to get even better!

So in a nutshell, in JavaScript, we have a few different ways to handle asynchronous-ity namely callback functions, Promises, and async-await.

[1]. Callbacks functions

[2]. Promises (since ES6) => resolved Callback Hell.

[3] Async/Await (since ES8) => work on group of Promises + generators

Callbacks

Callbacks are the most widely used functional programming paradigm. These anonymous functions in JavaScript are passed as arguments to other functions. Also, these can be executed or returned from that function to be executed later.

The timing of the callback function execution entirely depends on the calling function’s mechanism. This is the sole basis of the asynchronous or non-blocking behavior of the Node JS framework.

Image for post
Image for post

How to define a custom callback

When a callback function is passed as an argument to another function, only the function definition is passed.

Passing a custom callback as an argument

A callback function is a function you provide that will be executed after completion of the async operation. Let’s create a fake user data fetcher and use a callback function to do something with the result.

The Fake Data Fetcher

First we create a fake data fetcher that doesn’t take a callback function. Since fakeData doesn’t exist for 300 milliseconds, we don’t have synchronous access to it.

Image for post
Image for post

In order to be able to actually do something with our fakeData, we can pass fetchData a reference to a function that will handle our data!

Image for post
Image for post

Let’s create a basic callback function and test it out:

Image for post
Image for post

After 300ms, we should see the following logged:

Image for post
Image for post

Promises

Promises are generally used for easier handling of Asynchronous operations or blocking code, for example, file operations, API calls, DB calls, IO calls, etc. in the so called synchronous programming language called JavaScript.

In a synchronous execution, you don’t need states, per se. Your code either successfully execute or fail to execute. In asynchronous code, we execute, wait for callbacks and decide if its success or failure and continue with the synchronous code execution.

Promises “saves you from Callback hell”.

Syntax

Since we have moved on from ES5 to ES6, I am just gonna straightaway use the ES6 Syntax.

Image for post
Image for post
Basic Promises Syntax

Note: Here, the .then() method accepts both resolved and rejected callbacks, but catch accepts only rejected callbacks. In practice, always use the then method for handling resolved callbacks and catch for rejections to keep things cleaner and for proper funnelling of errors at a single exit point.

A promise is an object that may produce a single value some time in the future: either a resolved value, or a reason that it’s not resolved (e.g., a network error occurred).

  • Promises can either resolve or reject. When a Promise resolves, you can handle its returned value with .then then method. If a Promise is rejected, you can use the .catch the error and handle it.
  • A promise may be in one of 3 possible states: fulfilled, rejected, or pending. Promise users can attach callbacks to handle the fulfilled value or the reason for rejection.
Image for post
Image for post
  • Both .then and .catch will return a new promise., which means that promises can be chained.
  • Promises solved earlier Callback-Hell issue.
Image for post
Image for post
  • The main difference between Promise.allSettled and Promise.all and Promise.race is that the allSettled does NOT short-circuit.
  • .all and .race DO short-circuit:
  • .all stops evaluation if any promise is rejected and .race stops when any promise is either fulfilled or rejected.
  • Promise.allSettled() method returns a promise that resolves after all of the given promises have settled down, which means they are either resolved or rejected, with an array of objects that each describe the outcome of each promise.

◉ The Fake Data Fetcher (with Promises)

Let’s use the same fake data fetcher as before. Instead of passing a callback, we’re going to return a new Promise object the resolves with our user’s data after 300ms. As a bonus, we can give it a small chance of rejecting as well.

Image for post
Image for post

Our new fetchData function can be used as follows:

Image for post
Image for post

If fetchData successfully resolves (this will happen 90% of the time), we will log our user data as we did with the callback solution. If it is rejected, then we will console.error the error message that we created (“Fetch failed!“)

One nice thing about Promises is you can chain then to execute subsequent Promises. For example, we could do something like this:

Image for post
Image for post

Furthermore, we can pass an array of Promises to Promise.all to only take action after all Promises are resolved:

Image for post
Image for post

In this case, if both Promises are successfully resolved, the following will get logged:

Image for post
Image for post

Basic Promise With A Single .then() Method

This is probably the simplest form of Promise you will ever come across and is the key to understanding how Promises really work. We have a simple promise constructor and when the promise is resolved it executes the resolved-callback/handler in the then method. The resolved handler is not called until the promise is resolved, in other words, it waits for the promises to be resolved. This is the key to the asynchronous nature of the Promises.

Image for post
Image for post
Basic Promises with a single then method

Once the resolve method of the Promise constructor is called the resolve handler picks up the resolved value as an argument and prints it. Interestingly the then method doesn’t always behave this way in a Promise. Let’s see more of thens in the next section.

Promises With Multiple .then() Methods

Now that we have understood how Promises generally work, let’s look at how the then method works. As said above the then method accepts both resolve and reject handlers, but for the sake of simplicity, we are just going to use resolve handlers along with the then method.

.then method following a Promise constructor

Then methods following a Promise constructor or a Promise object doesn’t execute the resolve/reject handler until the Promise resolves/rejects. The then method attached to the Promise constructor is the first handler to get invoked on resolve/reject of the Promise. (Refer to the image above)

.then() method following another ‘then’ returning a value/reference

The then methods following other then methods have different behaviour for promises and generic methods. The seemingly-synchronous behaviour is when the previous then the method doesn’t return any promises. Looking at the example below,

Image for post
Image for post
Then methods executing synchronously (not recommended)

Once the promises are resolved the then methods linked to the promises start executing one after the other, here you can add any number of thens and it can go on forever. Literally forever. This is similar to a code inside a really long function, executing one after the other. Although this is asynchronous by-nature, this can be a bit confusing for beginners.

Note: one interesting behaviour of this is that when you do not return anything from the previous then method, x is undefined.

.then() method following another ‘then’ returning a Promise

When a then method returns a promise in a sequence, the execution is suspended until this promise resolved/rejected. This is otherwise called as ‘Promise Chaining’, let’s see about it in the next section.

Promise Chaining

This is the core of Promise usage and possibly something which you need to understand before you can understand concepts around Async/await and Redux-sagas. Let’s take a deep dive.

Let’s consider we have two promise blocks, (a Promise block is a Promise constructor followed by its own then methods). And we want one block to execute after the other, what do we do? We make use of the asynchronous behaviour of the then method.

When a then method returns a promise, the subsequent executions of the then are suspended until this promise block is resolved.

Let’s look at a more detailed example to understand further. In the following image, you can see clearly see three promise blocks and let’s understand how they form a promise chain.

Image for post
Image for post
Chaining promises

Explanation

  1. The promise1 is built with a constructor, and will automatically move to a pending state until resolved or rejected. At the instance of creation, this promise is still pending, which will get resolved after one second.
  2. Similarly, promise2 and promise3 will also be pending, which will get resolved after 2 and 3 seconds respectively. (we will see the order of resolutions in coming sections, don’t worry about it now)
  3. At the1st second, promise1 is resolved and the resolved value of ‘first’ appears in the resolve handler.
  4. The resolve handler returns another promise called promise2. Now the subsequent the thens following would have to wait till promise 2 is resolved.
  5. The promise2 executes all the attached then methods, before continuing to execute the promise1’s then methods. The value returned by promise2 block’s last then method appears in the next occurring then of the promise1 block.
  6. Now again the execution is suspended till promise3 is resolved before finishing the whole execution.

At first, the example might be a bit harder to understand, give it a good 10 minute read to wrap your head around it. Let it sink in before you move on to the next section.

Promise Pyramid or Nested Promises

The promise chain grows linearly from the top-down, but only problem in this approach is that we do not have access to the variables between different then methods or between different promise blocks.

Although there are different ways of achieving this, to keep it simpler we can use something called as Nested Promises. These nested promises will have access to variables from its outer scope, and are otherwise called as promise-pyramids, which will grow to the right. If not done correctly, this promise pyramid can eventually lead to something called as Promise-Callback Hell.

Let’s look at the problems accessing variables in promise-chaining below,

Image for post
Image for post
Problem with promise chaining

To ensure that all the consecutive promise blocks have access to all the parental scope variables, we can nest promises. Let’s look a simple two level nesting of promises to understand it better,

Image for post
Image for post
Nested promises

In the nested promises, you can see that promise2’s then method resolves and prints both the value from the outer scope and the inner scope.

A very important point to keep in mind is that during nesting sometimes we forget to return the nested promises, which will lead to an anti-pattern where your resolves/rejects will not bubble up to the top. Always!! I say always return a promise when its chained or nested.

( You can try rejecting the promise2 and verify whether catch method successfully catches the error or not, take it as a challenge — with and without a return statement before promise2)

Sometimes nesting promises become inevitable which will inevitably lead to the callback hell we talked about, hence to tackle this issue ES7 has come up with Async/await. Be sure to check it out when you have time.

Order of Execution of Promise Blocks

Once you have mastered the nesting and chaining of promises, its now time to understand how promise execution works. Many developers think that promises would always wait till they are called or made use of, but that’s just wrong understanding of promises. A promise would try to resolve/reject it self as soon as possible, if it can.

There are two important points to remember about the promise execution cycle in a normal promise block and in a chained/nested promise block.

  1. A promise once created races towards resolve/reject. If its resolved/rejected it changes its state to resolved and executes the attached then/catch method.
  2. In a promise chaining or nesting when you return a promise inside a then method, and if the returned promise is already resolved/rejected, it will immediately call the subsequent then/catch method, if not it will wait.

Let’s look at an example to understand better,

Image for post
Image for post
Order of execution of promise blocks

Explanation

  1. promise1, promise2 and promise3 are initialised and they are racing towards resolve/reject in time. Currently, it is zeroth second.
  2. In the1st second, all three promises are pending.
  3. In the 2nd second, promise2 has resolved.
  4. In the 3rd second, promise1 has resolved and the then handler is executed. In the then handler a resolved promise2 is returned. (a resolved promise holds resolved value and has a resolved state)
  5. Since promise2 is already resolved it immediately calls the next then method attached, without waiting.
  6. In the 3rd second the promise 3 is still pending because it needs two more seconds to be resolved.
  7. After 5th second all three promises are resolved.

Catching errors in Promises

Catching an error is pretty straightforward in promises, a catch method at the end of a promise block should do the trick. It handles both, when an error is thrown and when its rejected.

But throwing errors has its limitations in asynchronous coding, when we throw an error inside an async block of code it will never be caught. Alternatively, rejecting and returning a promise properly should work seamlessly without any issues and its considered the best practice.

Also, you can reject a promise in any of its attached then methods by simply returning a Promise.reject(customErrorValue);

Promise.all() vs Promise.race()

Promise all resolves/rejects all the promises that are passed as an iterable before calling the then handler with an iterable result, whereas Promise race condition resolves/rejects the fastest promise that it can resolve/reject. Read more on the official docs.

Anti-patterns

There are many anti-patterns which you can avoid in a promise, but the main culprit is not returning promises and values properly. In a synchronous sequence of .then methods following a Promise block, if a value is not returned from the previous .then method it will not appear in the next occurring then.

Image for post
Image for post
Then methods with no-returns

Similarly, when we use a promise chaining, we need to return the promises. When we don’t return the promises, the consecutive .then methods will not realise that it needs to wait for a promise in the sequence and simply assumes it as another line of code to be executed, there by losing its async nature.

Image for post
Image for post
Promise chaining with/without return statement

Note: Only in the promise constructor block you do not have to explicitly return a resolve/reject. Apart from that, in the consecutive then methods, every value and promise chained needs to be returned, for accessing it further down the block.

Bonus Coding Challenge

If you are interested in a Promise challenge, you can continue reading this para. Let’s assume that we have a for loop that prints 0 to 10 at random intervals (0 to 6 seconds). We need to modify it using promises to print sequentially 0 to 10. For example, if 0 takes 6 seconds to print and 1 takes two seconds to print, then 1 should wait for 0 to print and so on.

Hint: Use for loop to chain promises :).

Conclusion

Understanding Promises is the first step towards understanding Async/await and solving challenges faced in Promises using the ES7 alternatives.

Async/Await

Async/await is a new way to write Asynchronous code. Previous alternatives for asynchronous code are callbacks and promises.

  • It is build on top of Promises and is compatible with all existing Promise-based APIs.
  • Async/await is actually just syntax sugar built on top of promises. It cannot be used with plain callbacks or node callbacks.
  • Async/await is, like promises, non blocking.
  • Async/await makes asynchronous code look and behave a little more like synchronous code. This is where all its power lies.

The name comes from async and await - the two keywords that will help us clean up our asynchronous code:

Async

  • declares an asynchronous function

async function someName(){...}

The word “async” before a function means one simple thing: a function always returns a promise.

Even though you do not return a promise, JavaScript converts it to a Promise.

async function myFunc() {
return 1
}

The above code is equivalent to this.

function foo() {
return Promise.resolve(1)
}
  • Any async function always returns a promise implicitly.
  • When called, async functions resolve with whatever is returned in their body.
  • Async functions enable the use of await. One async function can have more than one ‘await’ expressions.

Await

  • pauses the execution of async functions.

var result = await someAsyncCall();

  • When placed in front of a Promise call, await forces the rest of the code to wait until that Promise finishes and returns a result.
  • Await works only with Promises, it does NOT work with callbacks.
  • Await can only be used inside async functions.

Here is a simple example that will hopefully clear things up:

Let’s say we want to get some JSON file from our server. We will write a function that uses the axios library and sends a HTTP GET request to https://tutorialzine.com/misc/files/example.json. We have to wait for the server to respond, so naturally this HTTP request will be asynchronous.

same implementation using Promises & Async/await

Below we can see the same function implemented twice. First with Promises, then a second time using Async/Await.

It’s pretty clear that the Async/Await version of the code is much shorter and easier to read. Other than the syntax used, both functions are completely identical — they both return Promises and resolve with the JSON response from axios. We can call our async function like this:

setTimeout along with async-await

setTimeout doesn’t return a Promise, that could be awaited. So, you will need to promisify it manually, as below:

getJSONAsync().then( function(result) {
// Do something with result.
});
async function f() {

let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});

let result = await promise; // wait until the promise resolves (*)

alert(result); // "done!"
}

f();

Cases where Promises still scores over Async-await

Q. So, does Async/Await make promises obsolete?

No, not at all. When working with Async/Await we are still using Promises under the hood. A good understanding of Promises will actually help you in the long run and is highly recommended.

There are even use cases where Async/Await doesn’t cut it and we have to go back to Promises for help.

[1]. Making multiple IN-dependent A-synchronous calls

One such scenario is when we need to make multiple IN-dependent asynchronous calls and wait for all of them to finish.

If we try and do this with async and await, the following will happen:

async function getABC() {
let A = await getValueA(); // getValueA takes 2 second to finish
let B = await getValueB(); // getValueB takes 4 second to finish
let C = await getValueC(); // getValueC takes 3 second to finish
return A*B*C;
}

Each await call will wait for the previous one to return a result. Since we are doing one call at a time the entire function will take 9 seconds from start to finish (2+4+3).

This is NOT an optimal solution, since the three variables A, B, and C aren't dependent on each other. In other words we don't need to know the value of A before we get B. We can get them at the same time and shave off a few seconds of waiting.

To send all requests at the same time a Promise.all() is required. This will make sure we still have all the results before continuing, but the asynchronous calls will be firing in parallel, not one after another.

async function getABC() {
// Promise.all() allows us to send all requests at the same time.

let results = await Promise.all([ getValueA, getValueB, getValueC ]);
return results.reduce((total,value) => total * value);
}

This way the function will take much less time. The getValueA and getValueC calls will have already finished by the time getValueB ends. Instead of a sum of the times, we will effectively reduce the execution to the time of the slowest request (getValueB - 4 seconds).

[2]. Handling Errors in Async/Await

Another great thing about Async/Await is that it allows us to catch any unexpected errors in a good old try/catch block. We just need to wrap our await calls like this:

async-await using try-catch

The .catch clause will handle errors provoked by the awaited asynchronous calls or any other failing code we may have written inside the try block.

If the situation requires it, we can also catch errors upon executing the async function. Since all async functions return Promises we can simply include a .catch() event handler when calling them.

async-await WITHOUT try-catch

It’s important to choose which method of error handling you prefer and stick to it. Using both try/catch and .catch() at the same time will most probably lead to problems.

Browser Support

Async/Await is already available in most major browsers. This excludes only IE11 — all other vendors will recognize your async/await code without the need of external libraries.

Experience with Front-end Technologies and MERN / MEAN Stack. Working on all Major UI Frameworks like React, Angular.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store