Make me a Promise
Let’s start with a short story.
A girl asks his father: Daddy are there people in space right now? The father doesn’t know but he promises he’ll find out.
At that point, the girl decides if there are people in space, she’ll write their names in the journal. If there aren’t she won’t. Also, if father is unable to find this out, she’ll ask mom.
Later, after some time, father comes back and he does one of the following:
-
He tells the girl the names of the people in space. The promise was fulfilled, father kept his promise, and the girl is writing the names in the journal.
-
He tells the girl there are no people in space. The promise was fulfilled, again father kept his promise, but the girl is not writing anything in the journal.
-
He tells the girl that he couldn’t find out. There was a problem, the promise was not fulfilled, father was not able to find out who’s in space, so the girl will ask mom.
Here is how the story is implemented in code:
function daddyMakesPromise() {
// makes a promise
return new Promise((resolve, reject) => {
// Daddy looks on the internet for the answer, this takes time
daddyLooksOnTheInternetForAnswer("")
.then((data) => {
// The promise is fulfilled
if (data) {
resolve("There are NO people in space right now!");
} else {
resolve("Yes, these people are in space: ", data);
}
})
.catch((error) => {
// Internet not working, father can't keep his promise
// There was a problem, the promise was not fulfilled
reject("Sorry honey, I don't know if there are people in space");
});
});
}
// One day daddy makes a promise
const promiseForGirl = daddyMakesPromise();
// Promise is pending!
promiseForGirl
.then((response) => {
// Later, when daddy fullfils the promise
if (response !== "There are NO people in space right now!") {
// write the names in the journal
}
})
.catch((error) => {
// Later, when daddy can't fulfil the promise:
// Ask mom
});
Why we need Promises?
Promises are supposed to help to make the code more clear, specifically to solve the callback hell problem.
Before Promises, we had to write code like this:
function a(callback) {
// do a XMLHttpRequest call
const result = ...;
// when done call the callback
callback(result);
}
function b(data, callback) {
// do a XMLHttpRequest call
const result = use(resultsFromA);
// when done call the callback
callback(result);
}
// This is the callback hell (simplified here)
// Error handling is hard, you need to make sure all callbacks accept errors
a(function(resultsFromA){
b(resultsFromA, function(resultsFromB){
c(resultsFromB, function(resultsFromC){
d(resultsFromC, function(resultsFromD){
...
})
})
})
})
If we use promise, the code is much clearer because from a .then() we can return a new Promise:
function a() {
return Promise((resolve, reject) => {
...
})
}
function b(data) {
return Promise((resolve, reject) => {
...
})
}
// This is much nicer
a()
.then(resultFromA => b(resultFromA))
.then(resultFromB => c(resultFromB))
.then(resultFromC => d(resultFromC))
.catch(error => {
// promises handle errors by default
// if any promise is rejected, the rest of the promises are ignored
})
Resolve and reject
When it’s created a Promise is in pending state. In the end, it should be fulfilled/resolved or rejected.
How to implement code that makes a promise:
return Promise((resolve, reject) => {
// to indicate that the promise was fulfilled call resolve with data
resolve(data);
// to indicated that the promise was rejected, call reject with an error
reject(error);
// if resolve/reject are never called, the promise will always be pending
});
then, catch, finally
How to implement code that receives a promise:
promise
.then(result => /* called when resolve() was called inside the Promise */ )
.catch(error => /* called when reject() was called inside the Promise */ )
.finally( _ => /* called when either resolve/reject was called inside the Promise */)
Note that in some browsers, finally is not available on Promises.
Working with several promises in the same time
Sometimes we can work with several promises in the same, for example, we can make several fetch calls to get some data.
We can use Promise.all()
to resolve promises in parallel.
Promise.all([promiseA, promiseB, promiseC])
.then((results) => {
// this code is executed after all the promises are fulfilled
// results is an array with individual results of each promise
})
.catch((error) => {
// this code is executed if at least one promise is rejected
// if one is reject, the rest are ignored
});
We can use Promise.race()
to resolve promises in parallel, but instead of waiting
for all to finish, it fulfills/rejects when the first promise is fulfilled or rejected.
Promise.race([promiseA, promiseB, promiseC])
.then((result) => {
// this code is executed after ONE the promises is fulfilled
})
.catch((error) => {
// this code is executed if at least one promise is rejected
// if one is reject, the rest are ignored
});
Finally, we have Promise.allSettled()
to work with promises in parallel.
This one waits for all promises to be settled
, either resolved or rejected.
Promise.allSettled([
promiseA,
promiseB,
promiseC
])
.then(results => {
// this code is executed after all the promises are settled
// some of them might be resolved, some might be rejected
results.forEach((result) => console.log(result.status)));
})
All these work by fulfilling the promises in parallel. There is no Promise method to call them one after the other but as shown before we can do that manually:
Promise.resolve()
.then(_ => promiseA)
.then(_ => promiseB)
.then(_ => promiseC)
.then(_ => promiseD)
.catch(error => {