DEV Community

loading...
Cover image for Async Functions - Chapter 2: Promises

Async Functions - Chapter 2: Promises

skaytech profile image skaytech Originally published at blog.skay.dev Updated on ・6 min read

Introduction

This is the second part of JavaScript Async Functions. If you have not read the first part, I would highly recommend that you do so before continuing.

Async Series Links
Part 1 Callback
Part 3 Async/Await

Promises

If you've ever been to a Starbucks during peak hours, you would have come across a scenario where the barista asks for your name and notes it down on your cup. When the order is ready, she calls out for you, so that you can pick up your order. Promises do exactly that.

Promises return back the execution status to the calling function to let them know what their current state of execution is.

Execution States

Promises are in one of the below three states:

  • Pending - This is the state when the execution is still under processing.
  • Resolved - This is the state when the promise is fulfilled and the resolution will be returned back to the calling function.
  • Rejected - This is the state when something has gone wrong and the error will be returned back to the calling function.

If you remember the callback example, once the main function passes over the control to the callback function, the responsibility of returning the status of execution to the main function solely lies on the callback function.

'Promises' solve this problem by returning back the status of execution. Let's look at how to create a promise.

Creating & Modifying a Promise

A promise can simply be created by calling the constructor.

const promise = new Promise();
Enter fullscreen mode Exit fullscreen mode

A callback is generally passed to a Promise with the resolve and reject status as parameters as shown below.

//A new promise is created and an anonymous function is passed on resolve & reject as the parameters
const promise = new Promise((resolve, reject) => {
    //After a timeout of 3 seconds the status of promise will be changed to resolved
    setTimeout(() => {
            resolve(); //Promise status changes to resolve
        }, 3000) //End of setTimeout method - After 3 sec resolve will be run
})

//Displays the status of the promise immediately. Note that the above function will run after 3 seconds
console.log('Status of Promise before being resolved: ', promise);

//Function will run after 5 seconds and hence will run after the promise has been resolved
setTimeout(() => {
    console.log('Status of Promise after being resolved: ', promise);
}, 5000); //Timeout set to 5 seconds

//Output
//Status of Promise before being resolved:  Promise {<pending>}

//After 5 seconds, the following will be output to the console
//Status of Promise after being resolved:  Promise {<resolved>: undefined}
Enter fullscreen mode Exit fullscreen mode

Things to note:

  • A promise is created using the 'new' constructor.
  • To the promise constructor, an anonymous function (callback) is passed with the 'resolve' and 'reject' parameters.
  • The above example uses ES6 arrow functions and setTimeout for delaying the execution of the function. If you like a refresher on ES6 arrow functions, you can read over here & about setTimeout function over here.
  • The anonymous function changes the promise state to resolved after 3 seconds.
  • Hence, the first statement will output the status of promise as 'pending'.
  • The second anonymous function setTimeout will output the status of promise as 'resolved' since the function runs after 5 seconds and by then the previous anonymous function would have run and changed the status of promise to resolve.

Then & Catch

Then & Catch are two methods of the JavaScript object that can be invoked. When a promise is resolved, then the function that is passed to the 'then' will be invoked. Likewise, when a promise is rejected, the function passed to the 'catch' will be invoked. Let us take a look with at the following examples:

Promise (resolved)

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(); //Promise is resolved after 3 seconds
    }, 3000)
});

promise.then(onSuccess); //the function 'onSuccess' will be invoked upon resolve()
promise.catch(onError);

function onSuccess() {
    console.log('The Promise has been resolved');
} //onSuccess() will be executed since the promise is resolved()

function onError() {
    console.log('An error has been encountered');
}
Enter fullscreen mode Exit fullscreen mode

Promise (rejected)

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(); //Promise is rejected after 3 seconds
    }, 3000)
});

promise.then(onSuccess);
promise.catch(onError); // the function 'onError' will be invoked on reject()

function onSuccess() {
    console.log('The Promise has been resolved');
}

function onError() {
    console.log('An error has been encountered');
} //onError() will be executed since the promise is rejected()
Enter fullscreen mode Exit fullscreen mode

Real Code Examples

Let us modify the previous example to use promise instead of callback.

//Define the Github User ID
const userId = 'skaytech';

/*
Function to fetch data using XMLHTTPRequest
The function uses Promise to resolve, reject based on the external API response
*/
const fetchData = function(userId) {

    return new Promise((resolve, reject) => {
        //Initialize xhr to a new XMLHttpRequest object 
        const xhr = new XMLHttpRequest();

        // Define the parameters to call an External API
        // Calling the Github getUsers API by userId
        // Params are - HTTP Method name, URL, Async (true/false)
        // When the third param is 'true', it means it's an asynchronous request
        xhr.open(
            'GET', `https://api.github.com/users/${userId}`, true);

        //The onload method will execute when a response has been received from external API
        xhr.onload = function() {
            //Checking for a response of 200 (It's a success (OK) response)
            if (xhr.status === 200) {
                //On success - resolve the promise and send response as a parameter
                resolve(xhr.responseText);
            } else {
                //On Error - reject the promise and pass the HTTP status as a parameter
                reject(xhr.status);
            }
        }

        //Upon Send the XMLHttpRequest will actual be processed
        //This is the method that actually triggers the API call
        xhr.send();
    });
}

//UI method to display the picture of Github User
function displayUserPicture(response) {
    const data = JSON.parse(response);
    const imgUrl = data.avatar_url;
    document.querySelector('#userimg').setAttribute('src', imgUrl);
}

//UI method to display Error if the Github User does not exits
function onError(status) {
    document.querySelector('#userimg').style.display = 'none';
    document.querySelector('#errorDiv').textContent = `Error Status: ${status}`;
}

//Invoke the fetch data function & pass the userId as a parameter
//then function is invoked upon success
//catch function will be invoked upon error
fetchData(userId)
    .then(response => displayUserPicture(response))
    .catch(err => onError(err));
Enter fullscreen mode Exit fullscreen mode

Things that have changed from the previous example:

  • The XMLHttpRequest is wrapped within a promise.
  • Upon Success, the promise is resolved & the response data is passed as a parameter to the displayUserPicture function.
  • Upon Error, the promise is rejected & the error is passed to the onError function.

You can play around with the code over here

Chaining

One last concept, before we are done and dusted with Promises. If you remember, we talked about how asynchronous programming generally isn't naturally tuned to the way we think. Chaining takes care of that and it is easier to explain with the following example.

/*
    A function that returns a resolved Promise after 2 seconds
    After a duration of 2 seconds, 'Wake up in the morning' is displayed on the console
*/
function getPromise() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('Wake up in the morning');
            resolve();
        }, 2000);
    });
}

function workout() {
    console.log('Workout');
}

function breakfast() {
    console.log('Breakfast');
}

function college() {
    console.log('College');
}

function sleep() {
    console.log('Sleep');

    throw new Error();
}

/*
    Promise Chaining in action
    Each then resolves and invokes the next function one by one
    For e.g. If getPromise() is successful, then workout() is invoked, and only if
    workout() is successful, then breakfast() is invoked and so on
*/
getPromise()
    .then(workout)
    .then(breakfast)
    .then(college)
    .then(sleep)
    .catch(err => console.log(err));

//Output
/*
Wake up in the morning
 Workout
 Breakfast
 College
 Sleep
 Error
    at sleep 
*/
Enter fullscreen mode Exit fullscreen mode

As you can see, chaining improves readability a lot and it's a lot easier to follow the code and it appears sequentially processing, while it is actually asynchronous in nature.

Conclusion

A quick recap on what we've covered in this article:

  • What is a Promise?
  • What are the execution states in a promise?
  • How to create and modify a promise?
  • Promise Chaining

We have covered two of the most important concepts in JavaScript. Now, let's proceed to article number 3 of the series Async/Await.

Discussion (0)

pic
Editor guide