For a long time I had put off learning about promises in JavaScript. I had heard that they were an alternative to callbacks i.e. they were used to manage asynchronous function calls in JavaScript. The first Node.js based app that I worked on was NodeBB and since it used callbacks I didn’t think that I needed to learn about promises. However times change.
After dabbling around in React.js I’ve figured that I have to learn about Promises if I want to be able to improve my JavaScript skills. In this post we’ll go through what promises are, what they are used for and how to use them in JavaScript.
What are Promises?
In Computer Science, Promises (also called futures, delays or deferred) are structures which are to used to manage concurrency in program execution. Many tasks could be running in parallel during program execution and Promises are used execute tasks in a series such that a second task executes only when the first completes. An example of this would be a database query. We need to make sure that a connection is established to the database first before we can query anything.
Why and How to use Promises in JavaScript?
As stated, Promises enable us to make sure that functions/tasks in JavaScript execute in a series one after the other. Consider for example the following code.
var firstName; var lastName; function setFirstName() { console.log("setFirstName called"); firstName = "Osama"; } function setLastName() { console.log("setLastName called"); lastName = "Arshad"; } function printName() { console.log("Name:", firstName, lastName); } setFirstName(); setLastName(); printName();
Here I define a very simple scenario. setFirstName
sets firstName
to “Osama
” and setLastName
sets lastName
to “Arshad
“. When executed, this code gives the output:
setFirstName called setLastName called Name: Osama Arshad
All is fine and good. But let’s say that our code had to retrieve the names from a database and the database query took around 100ms. For the sake of simplicity, I will simulate a wait using setTimeout
var firstName; var lastName; function setFirstName() { console.log("setFirstName called"); setTimeout(function() { firstName = "Osama"; }, 100); } function setLastName() { console.log("setLastName called"); setTimeout(function() { lastName = "Arshad"; }, 100); } function printName() { console.log("Name:", firstName, lastName); } setFirstName(); setLastName(); printName();
Now the output is
setFirstName called setLastName called Name: undefined undefined
See that both values are undefined
. This is because JavaScript does not wait for the setTimeout
in setFirstName
and setLastname
to complete before calling printName
. This is a consequence of the asynchronous nature of JavaScript. What that means is that we cannot make JavaScript wait for completion of a function before we call another function (at least not directly). So what is the solution to this problem?
Using Callbacks
One way is to use callbacks. Callbacks are functions that are passed as arguments to functions. They are executed once a particular task is complete. Using callbacks to manage program flow, the above code becomes
var firstName; var lastName; function setFirstName(callback) { console.log("setFirstName called"); setTimeout(function() { firstName = "Osama"; callback(); }, 100); } function setLastName(callback) { console.log("setLastName called"); setTimeout(function() { lastName = "Arshad"; callback(); }, 100); } function printName() { console.log("Name:", firstName, lastName); } setFirstName(function() { setLastName(function() { printName(); }); });
Output:
setFirstName called setLastName called Name: Osama Arshad
setFirstName
and setLastName
now receive callbacks as arguments and execute them once the setTimeout
s are completed. This appears to have solved our problem.
However, this is a fairly simple example. In non-trivial applications, callbacks are notorious for causing callback hell, making it very difficult to manage code where a lot of functions are dependent upon each other.
Using Promises
To avoid callback hell, we will use Promises. We can re-write the above code as:
var firstName; var lastName; var setFirstName = new Promise(function(resolve, reject) { console.log("setFirstName called"); setTimeout(function() { firstName = "Osama"; resolve(); }, 100); }); var setLastName = new Promise(function(resolve, reject) { console.log("setLastName called"); setTimeout(function() { lastName = "Arshad"; resolve(); }, 100); }); function printName() { console.log("Name:", firstName, lastName); } setFirstName .then(setLastName) .then(printName);
Output:
setFirstName called setLastName called Name: Osama Arshad
Each Promise object takes a function as input. That function takes two callbacks, resolve
and reject
. resolve
is called when the code in the Promise executes successfully while reject
is called if any error occurs. Promises are chained together using .then()
calls. In this way, any Promise in the .then
chain will only execute once the previous Promise has resolved. We can chain together an indefinite number of Promises together to perform actions in sequence.
We can also illustrate another behavior. The resolve
callback takes a parameter which is passed to the next thenable in the chain. Using this property we can re-write this as
var setFirstName = function() { return new Promise(function(resolve, reject) { console.log("setFirstName called"); setTimeout(function() { firstName = "Osama"; resolve(firstName); }, 100); }); }; var setLastName = function(firstName) { return new Promise(function(resolve, reject) { console.log("setLastName called"); setTimeout(function() { Name = firstName + " " + "Arshad"; resolve(Name); }, 100); }); }; function printName(Name) { console.log("Name:", Name); } setFirstName() .then(setLastName) .then(printName);
Hopefully after reading this you at least have a basic idea Promises and their use. If you notice any typos in the article, feel free to get in contact.