What you will learn
- Creating Promises
- Promise Executor function
- resolve and reject in Promise
- Consuming Promises
- Chaining Promises
- Catching errors in Promise
Disclaimer : I have used only arrow functions.
Why write Async code anyway?
JavaScript is a single-threaded programming language which means only a single statement is executed at a time. This means until a statement is completely executed it will not go to the next line of code.
This is a problem if you have a code snippet that takes a long time to complete such as an API call or reading a file from the disk.
To solve this we write asynchronous JavaScript code.
Creating New Promises
Promises are easy to create. Just create a function and return a new Promises
const longOperation = () => {
return new Promise ()
}
A promise takes an executor function as a parameter which again takes two parameters resolve
and reject
the code is easier to understand than my words.
const longOperation = () => {
return new Promise((resolve, reject) => {
// executor function
// your business logic here
});
};
Executor function
This is the place where you would write the synchronous code (or any code) which you want to run in the background. It has two arguments resolve
and reject
.
resolve
and reject
Think of these as return statements in a function. The Executor function should execute either resolve or reject based on your business logic. When the code inside the Executor function runs like expected without any errors, then execute the resolve function with the value you want to return. If anything goes wrong like 'file not found' or 'network error' return the error message using the reject function. I hope the following code will make it clear.
const longOperation = (a, b) => {
return new Promise((resolve, reject) => {
// executor function
try {
const result = a * b;
resolve(result);
} catch (error) {
reject(`Error resolving promise ${error}`);
}
});
};
Same example using if..else
const longOperation = (a, b) => {
return new Promise((resolve, reject) => {
// executor function
const result = a * b;
if(true){
resolve(result);
}
else{
reject(`Error resolving promise ${error}`);
}
});
};
Again
-
resolve(returnValue)
: Use this to return the result from successful execution of the business logic. -
reject(errorValue)
: Use this when your logic fails and you want to throw errors. This will trigger the catch block when the function is called inside atry...catch
block or the.catch()
when you consume your promise.
Consuming Promise
A promise can be consumed in two ways
-
.then().catch()
function -
async / await
function
Method 1 .then().catch()
This is the simplest way to consume a promise.
longOperation(5,6).then().catch()
When the Promise longOperation
runs without any errors the .then()
is executed. If there are any errors, the .catch()
is executed
longOperation(5, 5)
.then(result => console.log(result))
.catch(err => console.log(err));
console.log('This will be logged first'); // to demonstrate that promise is non-blocking
Output
This will be logged first
25
Explanation
- The
.then()
is executed if longOperation executes without any error, in other words, if the Promise isresolve
d - The
.catch()
is executed if longOperationreject
s the Promise - The
result
argument will contain the value passed to theresolve
- The
err
argument will contain the value passed to thereject
Note : The code console.log('This will be logged first');
is only used to demonstrate that Promises are non-blocking. Though it is callafter the longOperation
function call, it's being logged first in the console, this is because the longOperation
returns a Promise which runs in the background which makes JS available to execute the remaining code.
Method 2 async / await
Using async / await
is like sugar-coating what we saw earlier. Instead of using .then()
we are using a syntax which looks like synchronous code.
const main = async () => {
};
- Just declare a function like you will usually do.
- Add
async
keyword before the parenthesis of the arrow function. This will allow the function to useawait
keyword inside it.
const main = async () => {
try {
const result = await longOperation(10, 2);
console.log(result);
} catch (error) {
console.log(error)
}
};
main()
console.log('This will be logged first'); // to demonstrate that promise is non-blocking
Output
This will be logged first
20
Explanation
The variable result
will contain the resolved value from the promise longOperation
(i.e) it will contain the value passed inside the resolve()
.
When something goes wrong with longOperation
then the catch block is executed. The error variable contains the value passed inside the reject()
of the Promise.
Note: If you are using async...await then you should always consume promises inside a try...catch block.
Chaining Promises
Some times you want to chain Promises (i.e) you want to execute another Promise after completion of a Promise.
Chaining Promise using .then()
longOperation(5, 5)
.then(result => longOperation(10, result)) // multiply result by 10
.then(result => longOperation(100, result)) // multiply result by 100
.then(result => console.log(result)) // console log final result
.catch(err => console.log(err));
console.log('This will be logged first'); // to demonstrate that promise is non-blocking
OUTPUT
This will be logged first
25000
Note: Since I'm lazy to write imaginative Promise functions, I'm using the same longOperation to mimic a new promise. In reality, you will be calling different promises after the successful execution of one.
If any Promise in the chain throws an error then the .catch()
is executed.
Chaining Promise using async / await
const main = async () => {
try {
const result1 = await longOperation(10, 5);
const result2 = await longOperation(100, result1); // multiply result1 with 100
const result3 = await longOperation(1000, result2); // multiply result2 with 1000
console.log(result3); // only executed after all the Promises are resolved
} catch (error) {
console.log(error);
}
};
main();
console.log('This will be logged first'); // to demonstrate that promise is non-blocking
This will be logged first
5000000
Using async / await will make your code look tidy and readable unlike .then() in which you would have to write a lot of callbacks.
The catch block will be executed when any of the Promise throws an error.
Catching Errors in Promise
As we saw earlier, when any of the Promise executes the reject()
function then the catch block is executed. To demonstrate this we will create a new Promise.
const checkAndMultiply = (a, b) => {
return new Promise((resolve, reject) => {
// executor function
if (isNaN(a) || isNaN(b)) {
const error = 'Error: inputs are not numbers';
reject(error);
}
const result = a * b;
resolve(result);
});
};
checkAndMultiply
is a Promise which will only resolve if both the inputs passed to it are numbers else it will throw an error.
const main = async () => {
try {
const result1 = await longOperation(10, 5);
const result2 = await checkAndMultiply("text", result1);
const result3 = await checkAndMultiply(100, result2);
console.log(result3);
} catch (error) {
console.log(error);
}
};
main();
console.log('This will be logged first');
Output
This will be logged first
Error: inputs are not numbers
The first Promise longOperation
is resolved successfully
The second Promise checkAndMultiply
take string as one of its argument. So the Promise is rejected and the catch block is called without executing the next Promise in the code.
I hope this article might have helped you to understand Promises in JavaScript better. You can read more about Promise from MDN Web Docs.