Getting Started with JavaScript Promises

Asynchronous code is useful for performing tasks that are time-consuming but, of course, it’s not devoid of cons. Async code uses callback functions to process its results, however callback functions cannot return values that typical JavaScript functions can.

Thus, they not only take away our ability to control the execution of the function but also make error handling a bit of a hassle. This is where the Promise object comes in, as it aims to fill in some of the potholes in asynchronous coding.

Promise is technically a standard internal object in JavaScript, meaning it comes built-in to JavaScript. It is used to represent the eventual result of an asynchronous code block (or the reason why the code failed) and has methods to control the execution of the asynchronous code.

Syntax

We can create an instance of the Promise object using the new keyword:

new Promise(function(resolve, reject) {} );

The function passed as a parameter to the Promise() constructor is known as the executor. It holds the asynchronous code and has two parameters of the Function type, referred to as resolve and reject functions (more on these shortly).

States of the Promise object

The initial state of a Promise object is called pending. In this state, the result of the asynchronous computation does not exist.

The initial pending state changes to fulfilled state when the computation is successful. The result of the computation is available in this state.

In case the asynchronous computation fails, the Promise object moves to the rejected state from its initial pending state. In this state, the reason of the computation failure (i.e. error message) is made available.

To go from pending to fulfilled state, resolve() is called. To go from pending to rejected state, reject() is called.

Three states of Promises

The then and catch methods

When the state changes from pending to fulfilled, the event handler of the Promise object’s then method is executed. And, when the state changes from pending to rejected, the event handler of the Promise object’s catch method is executed.

Example 1

“Non-Promisified” code

Assume there’s a hello.txt file containing the “Hello” word. Here’s how we can write an AJAX request to fetch that file and show its content, without using the Promise object:

function getTxt() {
    let xhr = new XMLHttpRequest();
    xhr.open('GET', 'hello.txt');
    xhr.overrideMimeType('text/plain');
    xhr.send();
    xhr.onload = function() {
        try {
            switch (this.status) {
                case 200:
                    document.write(this.response);
                    break;
                case 404:
                    throw 'File Not Found';
                default:
                    throw 'Failed to fetch the file';
            }
        } catch (err) {
            console.log(err)
        }
    };
}

getTxt();

If the content of the file has been successfully fetched, i.e. the response status code is 200, the response text is written into the document. If the file is not found (status 404), a “File Not Found” error message is thrown. Otherwise, a general error message indicating the failure of fetching the file is thrown.

“Promisified” code

Now, let’s Promisify the above code:

function getTxt() {
    return new Promise(function(resolve, reject) {
        let xhr = new XMLHttpRequest();
        xhr.open('GET', 'hello.txt');
        xhr.overrideMimeType('text/plain');
        xhr.send();
        xhr.onload = function() {
            switch (this.status) {
                case 200:
                    resolve(this.response);
                case 404:
                    reject('File Not Found');
                default:
                    reject('Failed to fetch the file');
            }
        };
    });
}

getTxt().then(
    function(txt) {
        document.write(txt);
    }).catch(
    function(err) {
        console.log(err);
    });

The getTxt() function is now coded to return a new instance of the Promise object, and its executor function holds the asynchronous code from before.

When the response status code is 200, the Promise is fulfilled by calling resolve() (the response is passed as the parameter of resolve()). When the status code is 404 or some other, the Promise is rejected using reject() (with the appropriate error message as the parameter of reject()).

The event handlers for the then() and catch() methods of the Promise object are added at the end.

When the Promise is fulfilled, the handler of the then() method is run. Its argument is the parameter passed from resolve(). Inside the event handler, the response text (received as the argument) is written into the document.

When the Promise is rejected, the event handler of the catch() method is run, logging the error.

The main advantage of the above Promisified version of the code is the error handling. Instead of throwing Uncaught Exceptions around — like in the Non-Promisified version — the appropriate failure messages are returned and logged.

But, it’s not just the returning of the failure messages but also of the result of the asynchronous computation that can be truly advantageous for us. To see that, we’ll need to expand our example.

Example 2

“Non-Promisified” code

Instead of just displaying the text from hello.txt, I want to combine it with the “World” word and display it on the screen after a time-out of 2 seconds. Here’s the code I use:

function getTxt() {
    let xhr = new XMLHttpRequest();
    xhr.open('GET', 'hello.txt');
    xhr.overrideMimeType('text/plain');
    xhr.send();
    xhr.onload = function() {
        try {
            switch (this.status) {
                case 200:
                    document.write(concatTxt(this.response));
                    break;
                case 404:
                    throw 'File Not Found';
                default:
                    throw 'Failed to fetch the file';
            }
        } catch (err) {
            console.log(err)
        }
    };
}

function concatTxt(res) {
    setTimeout(function() {
        return (res + 'World')
    }, 2000);
}

getTxt();

On the status code 200, the concatTxt() function is called to concatenate the response text with the “World” word before writing it into document.

But, this code won’t work as desired. The setTimeout() callback function cannot return the concatenated string. What will be printed out to the document is undefined because that’s what concatTxt() returns.

“Promisified” code

So, to make the code work, let’s Promisify the above code, including concatTxt():

function getTxt() {
    return new Promise(function(resolve, reject) {
        let xhr = new XMLHttpRequest();
        xhr.open('GET', 'hello.txt');
        xhr.overrideMimeType('text/plain');
        xhr.send();
        xhr.onload = function() {
            switch (this.status) {
                case 200:
                    resolve(this.response);
                case 404:
                    reject('File Not Found');
                default:
                    reject('Failed to fetch the file');
            }
        };
    });
}

function concatTxt(txt) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(txt + ' World');
        }, 2000);
    });
}

getTxt().then(
    (txt) = > {
        return concatTxt(txt);
    }).then(
    (txt) = > {
        document.write(txt);
    }).catch(
    (err) = > {
        console.log(err);
    });

Just like getTxt(), the concatTxt() function also returns a new Promise object instead of the concatenated text. The Promise returned by concatTxt() is resolved inside callback function of setTimeout().

Near the end of the above code, the event handler of the first then() method runs when the Promise of getTxt() is fulfilled, i.e. when the file is fetched successfully. Inside that handler, concatTxt() is called and the Promise returned by concatTxt() is returned.

The event handler of the second then() method runs when the Promise returned by concatTxt() is fulfilled, i.e. the two seconds time-out is over and resolve() is called with the concatenated string as its parameter.

Finally, catch() catches all the exceptions and failure messages from both Promises.

In this Promisified version, the “Hello World” string will be successfully printed out to the document.

FacebookTwitterInstagramPinterestLinkedInGoogle+YoutubeRedditDribbbleBehanceGithubCodePenEmail