Asynchronous programming in Javascript

Asynchronous programming is managing data between multiple execution branches in an application. It is tricky when there are multiple executions are there and they interact with each other. Further more we may also needs some paths to be serial or parallel. This is how you can tackle this problems in Javascript.


Single threaded Synchronization

The main difference in Javascript’s execution is that it is single threaded. Though there can be multiple execution paths, for a given time it will be only one path. Javascript is not very well designed to be asynchronous initially. There are only handful of methods you can branch out from a execution cycle.

Mainly the complexity of synchronization of methods or variables is not a problem in javascript compared to fork in C or threads in Java. If a path is executing, that it will continue until it is finished before starting any other execution cycle.

The main downside is that if the current executing flow is blocked the whole app is blocked.

Example Use case

To better explain lets take an example of fetching ajax. Lets say we have two rest endpoints ‘/users/:id’ and ‘/post/:id’ when called will return json of a user and a post. To make it interesting lets fetch a user by his username and fetch the posts he has created.

Sample user object

{
id: 1,
username: 'TOM'
}

Sample post object

{
id: 1,
userId: 1,
title: 'some title'
}

So the requirement is to list posts a user has posted given the username. Lets assume we have a method that wraps ajax

Synchronized flow

This would be easiest. Everything is as expected except the code is blocking. Application will literary freeze until both the database calls succeed.

const API = 'https://jsonplaceholder.typicode.com';
// Data Fetching
const getData = (endpoint) => {
const request = new XMLHttpRequest();
request.open('GET', `${API}/${endpoint}`, false); // `false` makes the request synchronous
request.send(null);
return JSON.parse(request.responseText)
};
const findUserByUserName = (username) => getData(`users?username=${username}`)[0];
const getPostsByUserName = (userId) => getData(`posts?userId=${userId}`);

Now here comes the logic part of it.

// Logic
const getPostsByUserName = (username) => {
const user = findUserByUserName(username);
return getPostsByUserName(user.id);
}

Finally the exuction.

//Execution
console.log('Application Starts');
console.log('------------------');
console.log('Posts from Bret are');
const posts = getPostsByUserName('Bret');
posts.forEach((post, i) => console.log(`${i}. ${post.title}`));
console.log('Application Finishes');

You can check the code at JS Bin

Asynchronous flow

This is where things get interesting and non blocking. There are couple of choices here.

  1. Callbacks
  2. Promises
  3. Async Await

Lets look at each of them separately and see how the original code changes. There are couple of differences from the synchronous approach. One things that you might have noticed is that the app finishes before the data is fetched. Check the console.log

Callback

Now the code is getting cluttered. We need to pass around a callback for the calling function to return the data from the caller function. We also needs to pass around an error handler to handle error as well.

const API = 'https://jsonplaceholder.typicode.com';
// Data Fetching
const getData = (endpoint, callback) => {
const request = new XMLHttpRequest();
request.open('GET', `${API}/${endpoint}`); // the request is asynchronous
request.onreadystatechange = () => {
if (request.readyState == 4 && request.status == 200) {
const json = JSON.parse(request.responseText);
callback(json);
}
};
request.send(null);
};
const findUserByUserName = (username, callback) => 
getData(`users?username=${username}`, (users) => callback(users[0]));
const getPostsByUserName = (userId, callback) =>
getData(`posts?userId=${userId}`, (posts) => callback(posts));
// Logic
const getPostsByUserName = (username, callback) => {
findUserByUserName(username, (user) => {
getPostsByUserName(user.id, (posts) => {
callback(posts);
});
});
}
//Execution
console.log('Application Starts');
console.log('------------------');
console.log('Posts from Bret are');
const posts = getPostsByUserName('Bret', (posts) => {
posts.forEach((post, i) => console.log(`${i}. ${post.title}`));
});

Check JS bin

Promises

Now this is better compared to what we had previously. Code is nicely organized and the error handling is also trivial.

const API = 'https://jsonplaceholder.typicode.com';
// Data Fetching
const getData = (endpoint) =>
fetch(`${API}/${endpoint}`)
.then(resp => resp.json());
const findUserByUserName = (username) => 
getData(`users?username=${username}`)
.then(users => users[0]);
const getPostsByUserName = (userId) => getData(`posts?userId=${userId}`);
// Logic
const getPostsByUserName = (username) =>
findUserByUserName(username)
.then(user => getPostsByUserName(user.id));
//Execution
console.log('Application Starts');
console.log('------------------');
console.log('Posts from Bret are');
const postsPromise = getPostsByUserName('Bret');
postsPromise.then(posts => {
posts.forEach((post, i) => console.log(`${i}. ${post.title}`));
});
console.log('Application Finishes');

You can check the code at JS Bin

Async Await

Now this is the cool kid. This is not yet supported by all the browsers. Its an ES7 feature. But if you have a builder like babel, you do not need to worry about this. Lets see how the code is arranged.

const API = 'https://jsonplaceholder.typicode.com';
// Data Fetching
const getData = (endpoint) =>
fetch(`${API}/${endpoint}`)
.then(resp => resp.json());
const findUserByUserName = (username) =>
getData(`users?username=${username}`)
.then(users => users[0]);
const getPostsByUserName = (userId) => getData(`posts?userId=${userId}`);
// Logic
const getPostsByUserName = async(username) => {
const user = await findUserByUserName(username);
return await getPostsByUserName(user.id);
}
//Execution
console.log('Application Starts');
console.log('------------------');
console.log('Posts from Bret are');
const postsPromise = getPostsByUserName('Bret');
postsPromise.then(posts => {
posts.forEach((post, i) => console.log(`${i}. ${post.title}`));
});
console.log('Application Finishes');

This is just as what we had in synchronous code. Only the printing part needs to know about the promise and getPostByUserName was just as procedural as it was. Not there’s no JS bin code for this. you can try this at Babel REPL.

Conclusion

Lets look at the implementation of getPostsByUserName in all the above approaches as it is what actually handles the logic. It uses few ajax methods to complete its task. Here’s how it changes based on the approach.

// Synchronous code
const getPostsByUserName = (username) => {
const user = findUserByUserName(username);
return getPostsByUserName(user.id);
}
// Callbacks
const getPostsByUserName = (username, callback) => {
findUserByUserName(username, (user) => {
getPostsByUserName(user.id, (posts) => {
callback(posts);
});
});
}
// Promise
const getPostsByUserName = (username) =>
findUserByUserName(username)
.then(user => getPostsByUserName(user.id));
// Async await
const getPostsByUserName = async(username) => {
const user = await findUserByUserName(username);
return await getPostsByUserName(user.id);
}

Summery

Non blocking coding is difficult but it is needed in most of the cases to have a better user experience. Promise simplifies the use of callback and a better error handling in the asynchronous flow. Async await simplifies the code drastically and allows developers to write nearly as synchronous code.

Like what you read? Give Pasindu Rumal Perera a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.