Promises in JavaScript
VenomynusWhat is a Promise?
- A promise in JavaScript is similar to a promise in real life. When we make a promise in real life, it is a guarantee that we are going to do something in the future. Because promises can only be made for the future. A promise has two possible outcomes: it will either be kept when the time comes, or it won’t. This is also the same for promises in JavaScript. When we define a promise in JavaScript, it will be resolved when the time comes, or it will get rejected.
- Promises are one of the ways we can deal with asynchronous operations in JavaScript. The promise is commonly defined as a proxy for a value that will eventually become available. The Promise is a way of defining a function in such a way that we can synchronously control its flow.
- “Producing code” is code that can take some time. “Consuming code” is a code that must wait for the result. A Promise is a JavaScript object that links producing code and consuming code. JavaScript Promise object contains both the producing code and calls to the consuming code.
Creating a promise: The Promise constructor
To create a promise in JavaScript, you use the Promise constructor:
const myPromise = new Promise((resolve, reject) => {
// "Producing Code" (May take some time)
let condition;
if(condition is met) {
resolve('Promise is resolved successfully.');//when successful
} else {
reject('Promise is rejected'); // when error
}
});
The Promise constructor accepts a function as an argument. This function is called the executor.
The executor accepts two functions with the names, by convention, resolve() and reject().
When you call the new Promise(executor), the executor is called automatically.
Inside the executor, you manually call the resolve() function if the executor is completed successfully and invoke the reject() function in case of an error occurs.
The Promise object supports two properties: state and result. A promise is in one of these states:
- pending: initial state, neither fulfilled nor rejected.
- fulfilled: meaning that the operation was completed successfully.
- rejected: meaning that the operation failed.



Once the promise reaches either fulfilled state or rejected state, it stays in that state and can’t switch.
In other words, a promise cannot go from the fulfilled state to the rejected state and vice versa. It also cannot go back from the fulfilled state or rejected state to the pending state.
Once a new Promise object is created, it is in the pending state until it is resolved. To schedule a callback when the promise is either resolved or rejected, you call the methods of the Promise object: then(), catch(), and finally().


Consuming a Promise: then, catch, finally
1) The then() method
The then() method is used to schedule a callback to be executed when the promise is successfully resolved.
The then() method takes two callback functions:
promiseObject.then(onFulfilled, onRejected);
The onFulfilled callback is called if the promise is fulfilled. The onRejected callback is called when the promise is rejected.
2) The catch() method
If you want to schedule a callback to be executed when the promise is rejected, you can use the catch() method of the Promise object.
Internally, the catch() method invokes the then(undefined, onRejected) method.
// "Consuming Code" (Must wait for a fulfilled Promise)
myPromise.then((message) => {
console.log(message);
}).catch((message) => {
console.log(message);
});
3) The finally() method
Sometimes, you want to execute the same piece of code whether the promise is fulfilled or rejected.
To remove duplicate and execute the createApp() whether the promise is fulfilled or rejected, you use the finally() method, like this:
myPromise
.then(success => console.log(success))
.catch(reason => console.log(reason))
.finally(()=> createApp());
Promise Chaining
The instance method of the Promise object such as then(), catch()or finally() returns a separate promise object. Therefore, you can call the promise’s instance method on the return Promise. The successively calling methods in this way are referred to as the promise chaining.
Consider the following example. First, create a new promise that resolves to the value 10 after 3 seconds:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10);
}, 3000);
});
Note that we use the setTimeout() method to simulate an asynchronous operation. Then, invoke the then() method on the promise:
p.then((result) => {
console.log(result);
return result * 2;
});
The callback passed to the then() method executes once the promise is resolved. In the callback, we showed the result of the promise and returned a new value: result*2.
Because the then() method returns a new Promise whose value is resolved to the return value, you can call the then() method on the return Promise, like this:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10);
}, 3 * 100);
});
p.then((result) => {
console.log(result);
return result * 2;
}).then((result) => {
console.log(result);
return result * 3;
});
Output:
10
20
In this example, the return value in the first then() method is passed to the second then() method. You can keep calling the then() method successively as follows:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10);
}, 3 * 100);
});
p.then((result) => {
console.log(result); // 10
return result * 2;
}).then((result) => {
console.log(result); // 20
return result * 3;
}).then((result) => {
console.log(result); // 60
return result * 4;
});
Output:
10
20
60
The following picture illustrates the promise chaining:

Multiple handlers for a promise
When you call the then() method multiple times on a promise, it is not promise chaining. For example:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10);
}, 3 * 100);
});
p.then((result) => {
console.log(result); // 10
return result * 2;
})
p.then((result) => {
console.log(result); // 10
return result * 3;
})
p.then((result) => {
console.log(result); // 10
return result * 4;
});
Output:
10
10
10
In this example, you have multiple handlers for one promise. These handlers have no relationships. They execute independently and also don’t pass the result from one to another like the promise chaining above.
The following picture illustrates a promise that has multiple handlers:

In practice, you will rarely use multiple handlers for one promise.
Returning a Promise
When you return a value in the then() method, the then() method returns a new Promise that immediately resolves to the return value.
Also, you can return a new promise in the then() method, like this:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10);
}, 3 * 100);
});
p.then((result) => {
console.log(result);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result * 2);
}, 3 * 1000);
});
}).then((result) => {
console.log(result);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result * 3);
}, 3 * 1000);
});
}).then(result => console.log(result));
Output:
10
20
60
This example shows 10, 20, and 60 after every 3 seconds. This code pattern allows you to execute some tasks in sequence.
The following refactors the above example:
function generateNumber(num) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num);
}, 3 * 1000);
});
}
generateNumber(10)
.then(result => {
console.log(result);
return generateNumber(result * 2);
})
.then((result) => {
console.log(result);
return generateNumber(result * 3);
})
.then(result => console.log(result));
Promise chaining syntax
Sometimes, you have multiple asynchronous tasks that you want to execute in sequence. In addition, you need to pass the result of the previous step to the next one. In this case, you can use the following syntax:
step1()
.then(result => step2(result))
.then(result => step3(result))
...
If you need to pass the result from the previous task to the next one without passing the result, you use this syntax:
step1()
.then(step2)
.then(step3)
...
Suppose that you want to perform the following asynchronous operations in sequence:
- Get the user from the database.
- Get the services of the selected user.
- Calculate the service cost from the user’s services.
The following functions illustrate the three asynchronous operations:
function getUser(userId) {
return new Promise((resolve, reject) => {
console.log('Get the user from the database.');
setTimeout(() => {
resolve({
userId: userId,
username: 'admin'
});
}, 1000);
})
}
function getServices(user) {
return new Promise((resolve, reject) => {
console.log(`Get the services of ${user.username} from the API.`);
setTimeout(() => {
resolve(['Email', 'VPN', 'CDN']);
}, 3 * 1000);
});
}
function getServiceCost(services) {
return new Promise((resolve, reject) => {
console.log(`Calculate the service cost of ${services}.`);
setTimeout(() => {
resolve(services.length * 100);
}, 2 * 1000);
});
}
The following uses the promises to serialize the sequences:
getUser(100)
.then(getServices)
.then(getServiceCost)
.then(console.log);
Output:
Get the user from the database.
Get the services of admin from the API.
Calculate the service cost of Email,VPN,CDN.
300
Benefits of Promises
- Improves Code Readability
- Better handling of asynchronous operations
- Better flow of control definition in asynchronous logic
- Better Error Handling
Applications
- Promises are used for asynchronous handling of events.
- Promises are used to handle asynchronous HTTP requests.
Polyfill for Promises
Let us implement our polyfill (say PromisePolyFill). From the above we know the following :
- The promise constructor function must accept a callback as an argument. We will call it as executor.
- It must return an object with at least two properties, then and catch
- then and catch are functions that again accept a callback and also they can be chained. Hence both must return a reference to this
- We need to store the reference to callback function passed to then and catch somewhere so that they should be executed at a later point of time, depending on the status of the executor. If executor resolved we must invoke the then callback. If executor rejects, we must invoke catch callback.
- We must invoke this executor function which will accept two arguments, resolve and reject.
Now we require three more additional variables :
fulfilled : Boolean indicating if the executor has been resolved or not
rejected : Boolean indicating if the executor has been rejected or not
called: Boolean indicating if the then or catchcallback has been called or not.
In practice, you will rarely use multiple handlers for one promise.
Returning a Promise
When you return a value in the then() method, the then() method returns a new Promise that immediately resolves to the return value.
Also, you can return a new promise in the then() method, like this:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10);
}, 3 * 100);
});
p.then((result) => {
console.log(result);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result * 2);
}, 3 * 1000);
});
}).then((result) => {
console.log(result);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result * 3);
}, 3 * 1000);
});
}).then(result => console.log(result));
Output:
10
20
60
This example shows 10, 20, and 60 after every 3 seconds. This code pattern allows you to execute some tasks in sequence.
The following refactors the above example:
function generateNumber(num) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num);
}, 3 * 1000);
});
}
generateNumber(10)
.then(result => {
console.log(result);
return generateNumber(result * 2);
})
.then((result) => {
console.log(result);
return generateNumber(result * 3);
})
.then(result => console.log(result));
Promise chaining syntax
Sometimes, you have multiple asynchronous tasks that you want to execute in sequence. In addition, you need to pass the result of the previous step to the next one. In this case, you can use the following syntax:
step1()
.then(result => step2(result))
.then(result => step3(result))
...
If you need to pass the result from the previous task to the next one without passing the result, you use this syntax:
step1()
.then(step2)
.then(step3)
...
Suppose that you want to perform the following asynchronous operations in sequence:
- Get the user from the database.
- Get the services of the selected user.
- Calculate the service cost from the user’s services.
The following functions illustrate the three asynchronous operations:
function getUser(userId) {
return new Promise((resolve, reject) => {
console.log('Get the user from the database.');
setTimeout(() => {
resolve({
userId: userId,
username: 'admin'
});
}, 1000);
})
}
function getServices(user) {
return new Promise((resolve, reject) => {
console.log(`Get the services of ${user.username} from the API.`);
setTimeout(() => {
resolve(['Email', 'VPN', 'CDN']);
}, 3 * 1000);
});
}
function getServiceCost(services) {
return new Promise((resolve, reject) => {
console.log(`Calculate the service cost of ${services}.`);
setTimeout(() => {
resolve(services.length * 100);
}, 2 * 1000);
});
}
The following uses the promises to serialize the sequences:
getUser(100)
.then(getServices)
.then(getServiceCost)
.then(console.log);
Output:
Get the user from the database.
Get the services of admin from the API.
Calculate the service cost of Email,VPN,CDN.
300
Benefits of Promises
- Improves Code Readability
- Better handling of asynchronous operations
- Better flow of control definition in asynchronous logic
- Better Error Handling
Applications
- Promises are used for asynchronous handling of events.
- Promises are used to handle asynchronous HTTP requests.
Polyfill for Promises
Let us implement our polyfill (say PromisePolyFill). From the above we know the following :
- The promise constructor function must accept a callback as an argument. We will call it as executor.
- It must return an object with at least two properties, then and catch
- then and catch are functions that again accept a callback and also they can be chained. Hence both must return a reference to this
- We need to store the reference to callback function passed to then and catch somewhere so that they should be executed at a later point of time, depending on the status of the executor. If executor resolved we must invoke the then callback. If executor rejects, we must invoke catch callback.
- We must invoke this executor function which will accept two arguments, resolve and reject.
Now we require three more additional variables :
fulfilled : Boolean indicating if the executor has been resolved or not
rejected : Boolean indicating if the executor has been rejected or not
called: Boolean indicating if the then or catchcallback has been called or not.
function PromisePolyFill(executor) {
let onResolve, onReject;
let fulfilled = false,
rejected = false,
called = false,
value;
function resolve(v) {
console.log("resolve called")
fulfilled = true;
value = v;
//For async tasks like fetch , setTimeout resolve will be called after then
if (typeof onResolve === "function") {
console.log("onResolve in resolve",onResolve)
onResolve(value);
called = true;
}
}
function reject(reason) {
console.log("reject called")
rejected = true;
value = reason;
if (typeof onReject === "function") {
console.log("onReject in reject",onReject)
onReject(value);
called = true;
}
}
this.then = function (callback) {
console.log("then called")
onResolve = callback;
//when directly resolve a variable without any async tasks, first resolve after that then
if (fulfilled && !called) {
console.log("onResolve in then",onResolve);
called = true;
onResolve(value);
}
return this;
};
this.catch = function (callback) {
console.log("catch called")
onReject = callback;
if (rejected && !called) {
console.log("onReject in catch",onReject);
called = true;
onReject(value);
}
return this;
};
executor(resolve, reject);
}
//new PromisePolyFill((resolve) => setTimeout(() => resolve(1000), 0)).then(val => console.log(val));
/**
then called
resolve called
onResolve in resolve ƒ (val) {
return console.log(val);
}
1000
**/
//new PromisePolyFill((resolve,reject) => reject("error")).catch(val => console.log(val));
/**
reject called
catch called
onReject in catch ƒ (val) {
return console.log(val);
}
error
**/
new PromisePolyFill((resolve) => resolve(1000)).then(val => console.log(val));
/**
resolve called
then called
onResolve in then ƒ (val) {
return console.log(val);
}
1000
**/
I hope you have found this useful.
Thank you for reading :)