8-ES6++: Promises
Hasan Zohdy
Posted on November 19, 2022
Promises In JavaScript
In ES6 we have a new way to work with asynchronous code, which is called promises
. Promises are a new way to handle asynchronous code in JavaScript. They are a new way to write asynchronous code that is easier to read and write.
If you don't know what is asynchronous
code, let me tell you what is it.
Asynchronous code is code that doesn't run in order. For example, if you have a function that makes an HTTP request to get some data, that function will take some time to get the data, so the code after that function will run before the function finishes, that is asynchronous code
.
Usually it is handled using callbacks
, but promises
are a new way to handle asynchronous code.
If you are not familiar with callbacks
, let me tell you what is it.
Callbacks
are functions that are passed to another function as a parameter, and that function will call the callback function when it finishes, in our previous example we'll pass a callback function to the function that makes the HTTP request, and that function will call the callback function when it finishes.
Now back to our topic, promises
.
Promises are defined with new Promise()
, and it takes a function as a parameter, that function takes two parameters, resolve
and reject
, and we use them to resolve or reject the promise.
Let's see an example:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Hello World");
}, 1000);
});
promise.then((data) => {
console.log(data); // Hello World
});
So what does resolve
do? It resolves the promise A.K.A
it marks it as completed successfully
, and what does reject
do? It rejects the promise A.K.A
it marks it as failed
.
In the example above, we defined a promise that will resolve after 1 second, and then we used then
to get the data from the promise.
We can also use catch
to handle errors, where the failed
promise will be handled.
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject("Error");
}, 1000);
});
promise.catch((error) => {
console.log(error); // Error
});
So we can conclude that the promise receives a callback function which receives two parameters, resolve
and reject
, and the promise object is returned with couple of methods (so far), the then
method to handle the resolved success
promise, and the catch
method to handle the rejected failed
promise.
Chaining Promises
We can chain promises, and that is very useful when we have multiple asynchronous operations that depend on each other.
For example, Let's say we have a function to get the user data, and another function to get the user posts, and we want to get the user data first, then get the user posts, and then log the user data and the user posts.
const getUserData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
id: 155,
name: "Hasan",
age: 20,
});
}, 1000);
});
};
const getUserPosts = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["Post 1", "Post 2", "Post 3"]);
}, 1000);
});
};
getUserData()
.then((user) {
getUserPosts(user.id).then(posts => {
console.log(user, posts);
});
});
So we can see that we have two promises, the first one is to get the user data, and the second one is to get the user posts, and we want to get the user data first, then get the user posts, and then log the user data and the user posts.
In brief, we can use then
to get the user data, and then use then
again to get the user posts, and then log the user data and the user posts.
But there is a problem, what if we have more than two promises, and we want to get the user data first, then get the user posts, then get the user friends, and then get the user friends posts, and then log the user data and the user posts and the user friends and the user friends posts?
Here comes the magical solution, the async
and await
operators.
Async and Await
The async
and await
operators are used to handle promises in a more readable way, and they are used with promises
.
How they work? Let's see an example:
const getUserData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
id: 155,
name: "Hasan",
age: 20,
});
}, 1000);
});
};
const getUserPosts = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["Post 1", "Post 2", "Post 3"]);
}, 1000);
});
};
const getUserDataAndPosts = async () => {
const user = await getUserData();
const posts = await getUserPosts(user.id);
console.log(user, posts);
};
getUserDataAndPosts();
So we can see that we have two promises, the first one is to get the user data, and the second one is to get the user posts, and we want to get the user data first, then get the user posts, and then log the user data and the user posts.
Let's split the code into two parts, the first part is the async
function, and the second part is the await
operators.
The code before getUserDataAndPosts
is the same as the previous example, so we'll cover starting from const getUserDataAndPosts = async () => {
.
The async
keyword is used to define an async
function, and the await
keyword is used to wait for a promise to resolve, and then assign the resolved value to a variable.
So we can see that we have two promises, the first one is to get the user data, and the second one is to get the user posts, and we want to get the user data first, then get the user posts, and then log the user data and the user posts.
That where await
comes in, we use await
to wait for the promise to resolve, and then assign the resolved value to a variable.
We can't use await
outside of an async
function, so we have to define an async
function, and then use await
inside it.
Try and Catch
What if the promise fails? let's see how to handle the rejected
promise.
const getUserData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Error");
}, 1000);
});
};
const getUserPosts = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["Post 1", "Post 2", "Post 3"]);
}, 1000);
});
};
const getUserDataAndPosts = async () => {
try {
const user = await getUserData();
const posts = await getUserPosts(user.id);
console.log(user, posts);
} catch (error) {
console.log(error);
}
};
getUserDataAndPosts();
In the above example, we have rejected the promise in the first function which retrieves the user data, in that sense, the code will stop executing and will go to the catch
block, and the error will be logged, thus the getUserPosts
function will not be executed.
But if the opposite happened, the getUserData
function is resolved successfully, and the getUserPosts
function is rejected, it will execute the first function successfully but it will jump to the catch
block and log the error because the getUserPosts
function is rejected.
Promise.all
Now let's have another scenario, we have multiple promises but we need to wait for all of them to resolve, and then do something with the resolved values.
For example, let's say we have a function to get the user data, and another function to get the user posts, and we want to get the user data and the user posts at the same time, and then log the user data and the user posts.
Here we can use Promise.all
to wait for all promises to resolve, and then do something with the resolved values.
const getUserData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
id: 155,
name: "Hasan",
age: 20,
});
}, 1000);
});
};
const getUserPosts = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["Post 1", "Post 2", "Post 3"]);
}, 1000);
});
};
const getUserDataAndPosts = async () => {
const [user, posts] = await Promise.all([getUserData(), getUserPosts()]);
console.log(user, posts);
};
getUserDataAndPosts();
What we did here is we called Promise.all
method which receives an array of Promises
, it will wait until all promises are resolved successfully then it will return an array of the resolved values.
But what if one of the promises is rejected? let's see how to handle the rejected
promise.
const getUserData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Error");
}, 1000);
});
};
const getUserPosts = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["Post 1", "Post 2", "Post 3"]);
}, 1000);
});
};
const getUserDataAndPosts = async () => {
try {
const [user, posts] = await Promise.all([getUserData(), getUserPosts()]);
console.log(user, posts);
} catch (error) {
console.log(error);
}
};
getUserDataAndPosts();
The question here, would the Promise.all
continue trying to resolve the other promises if one of them is rejected? the answer is no, it will stop trying to resolve the other promises and will jump to the catch
block.
Finally Block
The finally
block is called in both scenarios, whether the promise is resolved Successfully done
or rejected Failed to resolve
.
Let's see how to use it first with the then
and catch
methods.
const getUserData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
id: 155,
name: "Hasan",
age: 20,
});
}, 1000);
});
};
const getUserPosts = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["Post 1", "Post 2", "Post 3"]);
}, 1000);
});
};
const getUserDataAndPosts = () => {
getUserData()
.then((user) => {
return getUserPosts(user.id);
})
.then((posts) => {
console.log(posts);
})
.catch((error) => {
console.log(error);
})
.finally(() => {
console.log("Done");
});
};
getUserDataAndPosts();
In the above example there will be two logs, the first one is the user posts which is called from then
method, and the second one is Done
which is called from finally
method.
Now let's see how to use it with async
and await
.
const getUserData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
id: 155,
name: "Hasan",
age: 20,
});
}, 1000);
});
};
const getUserPosts = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('Error');
}, 1000);
});
};
const getUserDataAndPosts = async () => {
try {
const [user, posts] = await Promise.all([getUserData(), getUserPosts()]);
console.log('Successfully Completed');
console.log(user, posts);
} catch (error) {
console.log('ERROR');
console.log(error);
} finally {
console.log("Done");
}
};
getUserDataAndPosts();
Here will be also two logs, the first one will be from the catch
block because the first promise is rejected, and the second one will be from the finally
block.
Promise.allSettled
The Promise.allSettled
method is similar to the Promise.all
method, but it will wait for all promises to be settled, whether they are resolved or rejected, and then it will return an array of objects, each object will have the status of the promise, and the value or the error.
Let's see how to use it.
const getUserData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
id: 155,
name: "Hasan",
age: 20,
});
}, 1000);
});
};
const getUserPosts = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Error");
}, 1000);
});
};
const getUserDataAndPosts = async () => {
const results = await Promise.allSettled([
getUserData(),
getUserPosts(),
]);
console.log(results);
};
getUserDataAndPosts();
The result will be an array of objects, each object will have the status of the promise, and the value or the error.
[
{
"status": "fulfilled",
"value": {
"id": 155,
"name": "Hasan",
"age": 20
}
},
{
"status": "rejected",
"reason": "Error"
}
]
Promise.any
The Promise.any
method is similar to the Promise.all
method, but it will wait for first promise to be resolved, and then it will return the resolved value.
Let's see how to use it.
const getUserData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
id: 155,
name: "Hasan",
age: 20,
});
}, 1000);
});
};
const getUserPosts = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Error");
}, 1000);
});
};
const getUserDataAndPosts = async () => {
const user = await Promise.any([getUserData(), getUserPosts()]);
console.log(user);
};
getUserDataAndPosts();
The result will be the resolved value.
{
"id": 155,
"name": "Hasan",
"age": 20
}
But what if the first promise is rejected and the second one is resolved, will it return the resolved value or the rejected error? the answer is the resolved value
.
const getUserData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Error");
}, 1000);
});
};
const getUserPosts = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["Post 1", "Post 2", "Post 3"]);
}, 1000);
});
};
const getUserDataAndPosts = async () => {
const posts = await Promise.any([getUserData(), getUserPosts()]);
console.log(posts);
};
getUserDataAndPosts();
Outputs:
["Post 1", "Post 2", "Post 3"]
So Promise.all
waits for all resolved promises but Promise.any
waits for the first resolved one.
Promise.race
The Promise.race
is from its name, will wait for the first resolved promise and resolve it regardless it is order in the array.
Let's see how to use it.
const getUserData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
id: 155,
name: "Hasan",
age: 20,
});
}, 2000);
});
};
const getUserPosts = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["Post 1", "Post 2", "Post 3"]);
}, 1000);
});
};
const getUserDataAndPosts = async () => {
const posts = await Promise.any([getUserData(), getUserPosts()]);
console.log(posts);
};
getUserDataAndPosts();
As we can see, the getUserData
will be delay with 2 seconds, but the getUserPosts
will be delay with 1 second, but the result will be the getUserPosts
because it is the first resolved promise.
Outputs:
["Post 1", "Post 2", "Post 3"]
A note before we conclude
Promises in Javascript has another name in other programming languages called Futures
, they are more or less the same concept, just wanted to let you know that because you might hear this name in other programming languages.
🎨 Conclusion
In this article, we learned the concept behind Promises
and why and how it is useful in our applications
We also learned about the Promise.all
, Promise.allSettled
, Promise.any
, and Promise.race
methods, and how to use them.
☕♨️ Buy me a Coffee ♨️☕
If you enjoy my articles and see it useful to you, you may buy me a coffee, it will help me to keep going and keep creating more content.
😍 Join our community
Join our community on Discord to get help and support (Node Js 2023 Channel).
📚 Bonus Content 📚
You may have a look at these articles, it will definitely boost your knowledge and productivity.
General Topics
- Event Driven Architecture: A Practical Guide in Javascript
- Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript
- After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
Packages & Libraries
- Collections: Your ultimate Javascript Arrays Manager
- Supportive Is: an elegant utility to check types of values in JavaScript
- Localization: An agnostic i18n package to manage localization in your project
React Js Packages
Courses (Articles)
Posted on November 19, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.