A beginners guide to Promise API in Javascript

Hassan Ahmed Khan
9 min readMay 4, 2018

--

Promise in Javascript is very much just like an everyday promise you make with your friend, parent or your spouse but there is a difference… Instead of being highly optimistic that the other person will keep the promise and won’t let you down it let you handle both situations. One when the promise is successfully kept. The other when it isn’t.

Now being little bit technical, a Promise is just a fancy way to defer some task for the future. Since you don’t know what future holds you prepare yourself for both the good and the bad and provide code to handle both situations, when the promise is fulfilled successfully, or when it couldn’t.

And if you are still confused just like me when I first encountered a Promise…

It is just what your old friend ‘callback’ does but in a much simpler and more efficient way.

Most of the time our Javascript code runs synchronously, i.e. one thing after the other but there are times when you need to do some work in the background while continuing your normal work. Most popular example of this is a network call. A network call takes some time to complete. Executing it synchronously halts your app until the network call is complete. A better approach is to make a network request in the background while continuing your normal flow of work. And when the network call is complete (either successfully or not) only then perform the related work.

Promise helps in this type of situation and let us continue our work while deferring the work of handling the network request once it is actually required to.

Note that a Promise doesn’t itself executes the code asynchronously. Its the responsibility of the person who creates the Promise to execute the code asynchronously.

An example scenario:

You are watching a video on Youtube. You start searching by typing some text in the search field. Youtube continues
to play your video and in the mean time shows the list of matching keywords in the search field. This or similar type of results can be acieved using a Promise.

Enough talk lets code. There is no better way to understand a programming concept other than programming it.

console.log("Hello to the Promise tutorial");let p = new Promise(function(resolve, reject) {
console.log("Promise started");
console.log("Promise is doing some important work...");
console.log("Promise has completed, will resolve shortly");
resolve("Promise resolved");
});

Save the above code in a file named promise.js. Execute it via nodejs using the command:

$node promise.js

You will see the following result:

Hello to the Promise tutorial
Promise started
Promise is doing some important work...
Promise has completed, will resolve shortly

Here we have created a new promise using a Promise constructor which takes a function as an argument. This function is called executer. The executer function contains whatever we like to accomplish with our promise. In this example its only some log statements.

Our executer function takes two arguments, namely resolve and reject. In its own resolve and reject are functions themselves. ‘resolve’ is called when some work being done by the executer function is complete. ‘reject’ is called when an error is encountered by executor and it couldn’t complete the work.

By convention ‘resolve’ method takes a result in its argument which could be a simple text message or a complex object. ‘reject’ takes an error object.

You will notice that in above code snippet, we never executed our promise or provided the ‘resolve’ and ‘reject’ method but our code runs automatically. This is a tricky part of a promise. A Promise executes as soon as it is created. For this behaviour and other special use-cases we almost always wrap a promise inside a function and whenever we want to execute the promise, we just call that function. We will look into that use-case in the coming section.

Lets edit promise.js to provide ‘reject’ and ‘resolve’ methods like so:

p.then(
function(message) {
console.log("Resolve: ", message);
},
function(error) {
console.log("Reject: ", error);
}
);

Promise provides a special ‘then’ function which is used to provide the resolve and reject callbacks.

Now executing the promise.js via node gives the following result:

Hello to the Promise tutorial
Promise started
Promise is doing some important work...
Promise has completed, will resolve shortly
Resolve: Promise resolved

It worked just like before but now since we provided resolve method, our promise executed it upon completion passing it a message.

The rejection works very similarly. Lets try rejecting our promise:

console.log("Hello to the Promise tutorial");let p = new Promise(function(resolve, reject) {
console.log("Promise started");
console.log("Promise is doing some important work...");
console.log("Promise has completed, will resolve shortly");
//resolve('Promise resolved');
reject("Error occurred");
});
p.then(
function(message) {
console.log("Resolve: ", message);
},
function(error) {
console.log("Reject: ", error);
}
);

The result is:

Hello to the Promise tutorial
Promise started
Promise is doing some important work...
Promise has completed, will resolve shortly
Reject: Error occurred

By now you have seen the basic working of a Promise. Lets wrap our promise inside a function so that we can control when exactly our Promise is called.

function doSomeStuff() {
return new Promise(function(resolve, reject) {
console.log("Promise started");
console.log("Promise is doing some important work...");
console.log(
"Promise has completed, will resolve shortly"
);
resolve("Promise resolved");
});
}
doSomeStuff().then(
function(message) {
console.log("Resolved: ", message);
},
function(error) {
console.log("Rejected: ", error);
}
);

In the above snippet we create a function named ‘doSomeStuff’. Inside the function we created a promise and returned it. The Promise is created the same way so there is nothing new here but notice that we are returning the newly created Promise. Later we called our ‘doSomeStuff’ function. Calling the function creates the Promise and returns it. It also started to execute it since as I mentioned earlier a Promise starts working as soon as it is created. We then call ‘then’ on the returned promise to provide our resolve and reject methods. This is the right pattern to use a Promise and a lot of asynchronous APIs are implemented this way. E.g: Axios [https://github.com/axios/axios] is a popular ‘Promise based HTTP client for the browser and node.js’. A simple get request in axios looks like:

axios
.get("/user?ID=12345")
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});

By now you can easily identify what is going on in the above piece of code. ‘get’ is a function defined in axios which takes a url as a parameter and returns a promise. We call then to provide resolve method. Response of the call is passed in the resolve method. Error is handled using catch here. Lets explore error handling now.

Error Handling:

There are two ways a promise can fail. One is by calling reject if something wrong happens. Other is by throwing an exception. So you can handle error by providing a ‘reject’ function. Or you can catch the error in the catch block. Both approaches are correct but using catch has its own benefits especially when chaining multiple promises together as we will see shortly.

let errorPromise = new Promise(function(resolve, reject) {
console.log("Promise started");
console.log("Promise is doing some important work...");
console.log("Promise is now raising an exception.");
throw "Exception raised";
resolve("Error promise resolved");
});
errorPromise
.then(
function(message) {
console.log("Success: ", message);
},
function(error) {
console.log("Failure: ", error);
}
)
.catch(function(error) {
console.log("Exception: ", error);
});

If you provide a reject method, throwing an error will automatically call it.
If you don’t provide a reject method but a catch method, throwing an error will call the catch method.

Even if some exception is thrown not by your own code but some other library that you used within the promise it will be handled by the catch function you provided.

Chaining Promise(s) together:

In the above example our doSomeStuff function returns a promise on which we call then to provide ‘resolve’ and ‘reject’ methods. We can repeat the same process in a resolve method to do more work and return a promise. In this way we can chain multiple Promises together.

function doSomething() {
return new Promise(function(resolve, reject) {
let count = 0;
count += 1;
resolve(count);
});
}
function resolve(count) {
return new Promise(function(resolve) {
count += 1;
resolve(count);
});
}
doSomething()
.then(resolve)
.then(resolve)
.then(function(count) {
console.log("Count: ", count);
});
// Output: Count: 3

In the above code snippet we create a generalised resolve method which creates a new Promise, increments the count and calls resolve. Notice that the Promise in ‘resolve’ function doesn’t take reject method, since we don’t need it, we can skip it.

Manipulating Data using multiple then(s):

We can call then multiple times on a Promise providing multiple ‘resolve’, ‘reject’ functions.

function doSomething() {
return new Promise(function(resolve, reject) {
let count = 0;
count += 1;
resolve(count);
});
}
function doSomething() {
return new Promise(function(resolve, reject) {
let count = 0;
count += 1;
resolve(count);
});
}
doSomething()
.then(function(count) {
return ++count;
})
.then(function(count) {
return ++count;
})
.then(function(count) {
console.log(‘Count: ‘, count);
});

Or in ES6:

doSomething()
.then(count => ++count)
.then(count => ++count)
.then(count => {
console.log("Count: ", count);
});

In above example we only provide ‘resolve’ but it can be replicated for reject in the same way.

When a Promise is fulfilled all the ‘resolve’ statements are executed in sequence. Similarly if promise is rejected, all the reject methods are executed in sequence.

Why Promise API?

If you have previously worked on Javascript you must be familiar with callbacks. Callback functions almost do the same work as the Promise do. Then why bother learning a new API? There are several benefits in using Promise. Main benefit is their execution sequence. A Promise guarantees the execution of ‘resolve’ or ‘reject’ methods only after the work is done. This is done by resolving the Promise in the next run-loop event.

Run-Loop:

A run-loop is a simple loop that runs infinitely or until the lifecycle of your app/program. It does two primary tasks.

  1. Check the task basket.
  2. Complete all the tasks in the task basket.
  3. Repeat.

When a Promise is created it is added as a task in the task basket. The run-loop picks it and executes the code. When it is finished executing it the ‘resolve’ or ‘reject’ method is not executed in the same loop. Instead they are add to the task basket to be picked in the next loop. This guarantees the correct execution sequence of the Promise.

To know exactly what I mean lets see some example. Run the following code in node:

console.log(‘Hello Promise’);let p = new Promise((resolve, reject)=> {
console.log(‘Promise started working’);
console.log(‘Promise is doing some important work…’);
console.log(‘Promise has completed, about to resolve’);
resolve(‘Promise resolved’);
});
p.then((message) => { console.log(‘Resolved!!!’)} );console.log(‘Normal work flow’);let count = 0
for (var i = 0; i < 10000; i++) {
count += 1;
}
console.log(‘Count: ‘, count);
setTimeout(() => {console.log(‘Timed out’);}, 0);console.log(‘Goodbye dear Promise’);

The result:

Hello Promise
Promise started working
Promise is doing some important work…
Promise has completed, about to resolve
Normal work flow
Count: 10000
Goodbye dear Promise
Resolved!!!
Timed out

The run loop thing will become clear if you understand what is happening in above example. Our program starts with a console-log statement. At this point lets assume we are at run-loop#: 1. Now we create a promise. The promise starts working right away in the same run-loop. All the console-logs in the Promise are printed next. The run-loop then finds the resolve statement but it doesn’t run it right away. Instead it schedules it for the next run-loop. Moving forward we do some work like doing a console-log and then looping for 1000 times just to busy the run-loop. We then calls setTimeout function to run with 0 interval. Our run-loop schedules the time-out function for the next run-loop as well. Now we have two things in our next run-loop bucket. Finally we print ‘Goodbye dear Promise’ in our run-loop # 1. Since there is no other work to do in this run-loop the loop finishes and second loop starts executing resolve method first and then time-out method.

Most of the time you will only consume a Promise like some fetch API that performs the network call and returns a Promise with response. You don’t write your own Promise very often but it is always a treat to learn an awesome API like that. Javascript Promise API is so handy that developers have started replicating it in other languages like this one in Swift.

--

--