DEV Community

Mohamed Ashiq Sultan
Mohamed Ashiq Sultan

Posted on

Learn Promises to write Asynchronous JavaScript code

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 ()
    }
Enter fullscreen mode Exit fullscreen mode

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
    });
};
Enter fullscreen mode Exit fullscreen mode

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}`);
        }
    });
};
Enter fullscreen mode Exit fullscreen mode

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}`);
            }
        });
    };
Enter fullscreen mode Exit fullscreen mode

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 a try...catch block or the .catch() when you consume your promise.

Consuming Promise

A promise can be consumed in two ways

  1. .then().catch() function
  2. async / await function

Method 1 .then().catch()

This is the simplest way to consume a promise.

longOperation(5,6).then().catch()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Explanation

  • The .then() is executed if longOperation executes without any error, in other words, if the Promise is resolved
  • The .catch() is executed if longOperation rejects the Promise
  • The result argument will contain the value passed to the resolve
  • The err argument will contain the value passed to the reject

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 () => {
};
Enter fullscreen mode Exit fullscreen mode
  • 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 use await 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
    });
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)