To get a job as a front end developer, we need to nail the coding interview.
Promises are important parts of JavaScript because of the pervasive use of asynchronous programming.
In this article, we’ll look at some common questions about JavaScript promises.
What are Promises?
Promises are one way to handle asynchronous operations in JavaScript. Each promise represents a single operation.
It’s made to solve the problem of combining multiple pieces of asynchronous code in a clean way.
With promises, we can avoid nesting callbacks too deeply.
For example, instead of writing deeply nested callbacks as follows:
const fs = require('fs');
fs.readFile('somefile.txt', function (e, data) {
fs.readdir('directory', function (e, files) {
fs.mkdir('directory', function (e) {
})
})
})
We write:
const fs = require('fs');
const promiseReadFile = (fileName) => {
return new Promise((resolve) => {
fs.readFile('somefile.txt', function(e, data) {
resolve(data);
})
})
}
const promiseReadDir = (directory) => {
return new Promise((resolve) => {
fs.readdir(directory, function(e, data) {
resolve(data);
})
})
}
const promiseMakwDir = (directory) => {
return new Promise((resolve) => {
fs.mkdir(directory, function(e, data) {
resolve(data);
})
})
}
promiseReadFile('somefile.txt')
.then((data)=>{
console.log(data);
return promiseReadDir('directory')
})
.then((data)=>{
console.log(data);
return promiseMakwDir('directory')
})
.then((data)=>{
console.log(data);
})
We chained promises instead of nesting them as follows:
promiseReadFile('somefile.txt')
.then((data)=>{
console.log(data);
return promiseReadDir('directory')
})
.then((data)=>{
console.log(data);
return promiseMakwDir('directory')
})
.then((data)=>{
console.log(data);
})
This is why we want to write promises for long chains of promises instead of deeply nested callbacks. It’s much easier to read.
Promises have 3 states. They can be pending, which means the outcome isn’t known yet since the operation hasn’t start yet.
It can be fulfilled, which means that the asynchronous operation is successful and we have the result.
It can also be rejected, which means that it failed and has a reason for why it failed.
A promise can be settled, which means that it’s either fulfilled or rejected.
Also, in the code above, we called the resolve
function to return the value of the promise. To get the resolved value of the promise, we get the resolved value from the parameter of the callback.
data
in each callback has the data that’s resolved from the promise above it.
To run the next promise in the callback, we returned the next promise.
To reject a promise, we call the reject
function in the callback that we pass into the Promise
constructor.
For example, we can change the code above to the following:
const fs = require('fs');
const promiseReadFile = (fileName) => {
return new Promise((resolve, reject) => {
fs.readFile('somefile.txt', function(e, data) {
if (e){
reject(e);
}
resolve(data);
})
})
}
const promiseReadDir = (directory) => {
return new Promise((resolve, reject) => {
fs.readdir(directory, function(e, data) {
if (e){
reject(e);
}
resolve(data);
})
})
}
const promiseMakwDir = (directory) => {
return new Promise((resolve, reject) => {
fs.mkdir(directory, function(e, data) {
if (e){
reject(e);
}
resolve(data);
})
})
}
promiseReadFile('somefile.txt')
.then((data)=>{
console.log(data);
return promiseReadDir('directory')
})
.then((data)=>{
console.log(data);
return promiseMakwDir('directory')
})
.then((data)=>{
console.log(data);
})
.catch((err)=>{
console.log(err);
})
Once a promise rejected, the ones that come after it won’t run.
We can get the error details and do something with it in the catch
function’s callback like we have above.
In the code above, we converted each asynchronous code into a promise by creating a function and then returning a promise in it that calls resolve
to fulfill the promise and reject
to reject the promise with an error value.
What is async/await?
async/await
is a new way to write promise chains in a shorter way.
For example, instead of calling the then
function with a callback like we had above, we rewrite it to:
try {
const readFileData = await promiseReadFile('somefile.txt');
console.log(readFileData);
const readDirData = await promiseReadDir('directory');
console.log(readDirData);
const makeFileData = await promiseMakwDir('directory');
console.log(makeFileData);
return makeFileData;
}
catch(err){
console.log(err);
}
})();
This is the same as:
promiseReadFile('somefile.txt')
.then((data)=>{
console.log(data);
return promiseReadDir('directory')
})
.then((data)=>{
console.log(data);
return promiseMakwDir('directory')
})
.then((data)=>{
console.log(data);
})
.catch((err)=>{
console.log(err);
})
As we can see, async/await
is much shorter than writing then
with callbacks.
We don’t have callbacks and we don’t have to return promises in each callback.
readFileData
, readDirData
, and makeFileData
are the same as the data
parameter in each then
callback.
try...catch
in the async
function is the same as the catch
call and callback at the end. err
has the same error object in both examples.
async
functions can only return promises, so makeFileData
is actually the resolved value of a promise, not the actual value.
Even though it looks like a synchronous function, it doesn’t behave the same.
Conclusion
Promises are a way to chain asynchronous operations cleanly without deeply nested callbacks.
We can make them even shorter and cleaner with the async/await
syntax.