Callbacks, Promises and Async/Await

Esakkimuthu E
6 min readJul 29, 2019

--

Javscript is single-threaded. Each browser window has only one Javascript thread running inside them. If javascript engine is excuting some function at that time user interacts with web page, click event fires because of the interaction and the javascript engine is busy doing some other works, this event would be queued in the Event Queue.

Javascript handling the Event Queue would look something like.

if (getData()) { 
processData();
}

getData() waits to get a data synchronously in the queue and when it gets the data, it processes that message. So if someone says that Javascript is multi-threaded, means that you can manipulate JavaScript to behave in an asynchronous way. Here are a ways that Javascript achive asynchronous.

Callbacks

Callbacks are the default Javascript technique for asynchronous work. In Javascript function are first class objects. We can pass objects into functions as arguments also we can pass functions into other functions as arguments.

Callback function is a function which will get called after execution of the first function and it will run as the second function.

function loadImage(src, callback) { 
let img = document.createElement('img');
img.src = src;
img.onload = () => callback(img);
document.body.appendChild(img);
}
loadImage('first-image.png', img => {
console.log('image is loaded');
})

This works well, but there are some questions without obvious answers. For instance, how do we handle errors in callbacks?

It’s best practice to assume that any operation could fail at any time. What is the image loading fails? Below is the imporved version of loadImage.

loadImage('first-image.png', (img, error) => {
if (error) {
// handle error
} else {
console.log('image is loaded');
}
})

Once again, The code that we used for loadImage is actually quite common, this will let an error at else part. So do we pass another function with another callback here. This scenorio will look like.

loadImage('first-image.png', (img, error) => {
loadImage ('second-image.png', (img, error) => {
loadImage ('farther-down.png', (img, error) => {
loadImage ('last-image.png', (img, error) => {
...
})
})
})
})

As it become more nested, The code becomes more diffcult to manage. Especially if we have a real code instead of …, That amy include more loops. This Scenorio that leads to called “callback hell” or “pyramid of doom”.

Promises

Promise are easy to manage when dealing with multiple asynchronous operation where callbacks can create callback hell leading to unmanageable code.

Promise object represents the eventual completion or failure of an asynchronous operation and the resulting value. Promise objectwill be in of the 3 possible states.

Fulfilled : Action related to promise is succeded. ie. resolve() was called.

Rejected : Action related to promise is failed. ie. reject() was called.

Pending : Promise is still pending. ie. promise is not fulfilled or rejected yet.

A Promise is settled if it is not pending, promise is fulfilled or rejected. Below is the syntax of promise.

const myPromise = new Promise((resolve, reject) => {
// do something asynchronous
resolve(some value); //fulfilled
//or
reject(); //rejected
})

The function with arguments resolve and reject passed to new Promise is called executer. The executer function calls immediately when the promise function is created.

image form MDN

Promise object has two internal properties state and result. Where state initially in pending state, once the promise is settled it changes to fulfilled or rejected. The result is initially undefined once the promise is settled it has value or error.

new Promise(function(resolve, reject){
let img = document.createElement('img')
img.src= 'image.jpg';
img.onload = resolve();
img.onerror = reject();
document.body.appendChild(img);
})
.then(finishLoading())
.catch(showAlternateImage());

In this example, wrapping an image tag loader in a promise because we are going to do some work after the image loads on a page. Image tag’s onload handler to specify success onload calls resolve after that .then executes. In this case it is the last piece of work effectively it’s the end of the function however, that’s not always the case if the image onload handle fails to resolve then the onerror handler will execute.

When either resolve or reject has been called the promise has been settled and at that point the next part os the chain executed usually a .then or .catch, There can be only a single result or error. The executer should call only one resolve or reject. The promise’s state change is final, all further calls of resolve and reject are ignored.

Error Handling Strategies

Asynchronous actions may fail sometimes. So far error handling has come in the form of .catches like below.

fetch('example.json')
.then(resolveFunction())
.catch(rejectFunction());

The other way of handling error

fetch('example.json')
.then(resolveFunction())
.then(undefined, rejectFunction());

These two chunks of code are actually equivalent .catch is just shorthand for then undefined and then a rejection function. The full function signature for then is

fetch('example.json')
.then(resolveFunction(), rejectFunction());

If the function gets resolve then the resolve function gets called, if the function gets rejected then the reject function gets called.

fetch('example.json')
.then(undefined, rejectFunction())
.then(...);

The code of the a promise executer and promise handlers has a invisble try…catch around it. If an exception happens it gets caught and treated as rejection.

fetch('example.json')
.then(function(data){
updateView(data);
return fetch(data.anotherUrl);
})
.catch(function(error){
console.log(error);
return recoverFromError();
})
.then(doSomething)
.catch(error) {
console.log(error)
}

In all cases, as soon as a promise rejects the control jumps to the closest rejection handler down the chain. The easiest way to catch all errors is to append .catch to the end of the chain.

What happened when an error is not handled?

new Promise(function(){
noSuchFunction(); //IF an error here
})
.then(() => {
// Do Something
});
// without .catch at the end

Javascript engine track such rejections and generates global error, unhandledrejection event is used to catch such errors.

window.addEventListener('unhandledrejection', function(e){
// event object has two special properties
console.log(e.promise) // promise that generates error
console.log(e.reason) // unhandled error object
});

Promise methods

The promise object itself has four methods.

resolve()

reject()

all()

race()

Promise.resolve()

This method returns a promise object that is resolve with a given value. If the value is a promise that promise is returned. If the value us a thenable the returned promise will follow that thenable.

let promise = Promise.resolve(123);promise.then(function(value){
console.log(value);
//123
})

Promise.reject()

The promise.reject() method works like promise.resolve(), but instead returns a promise object that is immediately rejected. It accepts one argument the error that can be passed to the .catch() function.

let promise = new Promise((resolve, reject) => reject(error));

Promise.all()

This method offers us a way of dealing with multiple promise together. let’s say we want to run many promise to execute in parallel and wait till all of them are ready.

If all the promise within the iterable list are successfully resolved. It will return an array of the values to be read by the .then() function.

let collectData = [
'example1.json',
'example2.json',
'example3.json'
];
let requests = collectData.map(data => get(data));Promise.all(requests)
.then(response => response.forEach(
(response) => {
console.log(`${response.title} : ${response.status}`);
}
))
.catch((error) => {
console.log(error);
})

Promise.race()

Similar to Promise.all it takes an iterable of Promises, but insteaded of waiting for all of them to finish it wait for the first result or error and goes on with it.

Async/Await

await is a new operator used to wait for a promise to resolve or reject. It can only be used inside async function. async function works like.

async function myFunction() {
try {
const fulfilledvalue = await Promise;
}
catch(rejectValue){
//...
}
}

async keyword used before the a function definition and await a promise, The function is passed in a non-blocking way the promise settles. If the promise fulfills we will get value back, if rejects catch block will log the error.

async function getData() {
try {
const resolve = await fetch('example.json');
const data = await response.json();
updateView(data);
}
catch((error) => {
console.log(error);
})
}

If we forgot to add try…catch block, then we get an unhandled promise error we can catch such error using global event handler unhandledrejection.

--

--

Esakkimuthu E

Front End Developer with over eight years of commercial experience, Passionate about front-end architecture, performance, scalable code, and thoughtful design.