loading...

Javascript Promise Chaining & Error Handling

azizhk110 profile image Aziz Khambati ・3 min read

The following might look like an opinionated article, please give your opinions as well. I am actually just publishing this to validate my thoughts.
Do recommend if you agree with it.

Improving a Piece of Bad Code

So lets start with bad code. This is an example of someone who hasn't escaped callback Hell.

function getHotDog () {
    return new Promise(function (resolve, reject) {
        getBun().then(function (bun) {
            addSausage(bun).then(function (bunWithSausage) {
                addSauce(bunWithSausage).then(function (hotdog) {
                    resolve(hotdog)
                })
            })
        })
    })
}

Here all the underlying function calls return Promises, so there is no need to create new Promise() but rather use return directly.

function getHotDog () {
    return getBun().then(function (bun) {
        return addSausage(bun).then(function (bunWithSausage) {
            return addSauce(bunWithSausage).then(function (hotdog) {
                return hotdog
            })
        })
    })
}

Now because we can just pass the output of one function to another, we can just chain them like this

function getHotDog () {
    return getBun().then(function (bun) {
        return addSausage(bun)
    }).then(function (bunWithSausage) {
        return addSauce(bunWithSausage)
    }).then(function (hotdog) {
        return hotdog
    })
}

Now because our functions are simple, we can even compact it down to this

function getHotDog () {
    return getBun()
    .then(addSausage)
    .then(addSauce)
}

Simple and easy to understand isn't it.

Multiple Variables

Now lets take an example of where you need two variables.
Lets say for example you had a type of sausage and you need a particular type for bun for it. In this example we need the responses of two promises and one is dependent on another.

function getSausage () {
    return Promise.resolve({ // Can be an async func.
        type: 'soya',
        length: Math.ceil(Math.random()*10)
    })
}
function getBun (len) {
    return Promise.resolve({ // Can be an async func.
        type: 'wholewheat',
        length: len
    })
}
function addSausageToBun (bun, sausage) {
    return Promise.resolve({ // Can be an async func.
        bun: bun,
        sausage: sausage
    })
}

function getHotDog () {
    return getSausage().then(function (sausage) {
        return getBun(sausage.length).then(function (bun) {
            // Over here we need both variables sausage & bun,
            // So that is why we are creating a closure.
            // Where we have both.
            return anddSausageToBun(bun, sausage)
        })
    }).then(addSauce)
}

You can extract the inner function out to another function, but you will be creating your closure there, so its going to be one and the same thing.

There is another option of using Promise.all but I feel that makes the code difficult to maintain. But its a personal choice.

function getHotDog () {
    return getSausage().then((sausage) => {
        return Promise.all([getBun(sausage.length), sausage]
    }).then(([bun, sausage] => {
        return anddSausageToBun(bun, sausage)
    }).then(addSauce)
}

Error Handling

Now that we know how to chain promises, lets see some error handling. Can you differentiate between these two?

function requestA(url) {
    return fetch(url).then(function (res) {
        return doSomething(res.data)
    }).catch(function (err) { // <- See this <<<<<<<<
        return fallbackForRequestFail()
    })
}

function requestB(url) {
    return fetch(url).then(function (res) {
        return doSomething(res.data)
    }, function (err) { // <- Change here. <<<<<<<<<<
        return fallbackForRequestFail()
    })
}

There is a very important difference between the above two examples, people who only recently have moved from jQuery to React/Redux Stack who would have started using Axios, Fetch or other similar libraries feel that React started eating up their errors rather than propagating them forward.

But its not React / Redux eating your errors.

Lets say for example you have a Runtime Error in the doSomething function above, in the requestA flow of things, your error would go into the catch and then fallbackForRequestFail would be called, when actually it should be fallbackForRuntimeError or should be logged so that you are notified rather than it just mysteriously disappearing.

function requestA(url) {
    return fetch(url).then(function (res) {
        // Gets called if request succeeds
        return doSomething(res.data)
    }).catch(function (err) {
        // Gets called if request fails, or even if .then fails
        return fallbackForRequestFail()
    })
}

function requestB(url) {
    return fetch(url).then(function (res) {
        // Gets called if request succeeds
        return doSomething(res.data)
    }, function (err) {
        // Gets called if request fails
        return fallbackForRequestFail()
    })
}

So in essence in the requestB flow of code, only function will be called amongst the two. And any other error like Runtime Error will be propagated ahead which you can handle individually or just log such errors to your error logging service and fix them on a case by case basis.

Log Unhandled Rejected Promises

In the Browser

window.addEventListener('unhandledrejection', event => {
    // Can prevent error output on the console:
    event.preventDefault();

    // Send error to log server
    log('Reason: ' + event.reason);
});

In Node.js

process.on('unhandledRejection', (reason) => {
    console.log('Reason: ' + reason);
});

Read more about logging unhandled rejected Promises here: http://www.2ality.com/2016/04/unhandled-rejections.html

This article was cross-posted from Medium

Posted on by:

azizhk110 profile

Aziz Khambati

@azizhk110

Frontend @ Anarock. Previously at Housing.com. BITS Pilani Alum

Discussion

markdown guide
 

Ive found this article useful for some time. However, I find the examples (and to be fair, the same is true for every "promise example" article), to be somewhat contrived. I rarely write promises where Im not testing the output and performing some action, which may or may not lead to another promise. An article on that would be gold. Anyhow, thanks

 

Nice post! For fetch requests, I prefer to check res.ok instead of using the error handler.

 

oh cool, we use axios which takes a validateStatus function parameter to determine whether the Promise should succeed or fail.

Though we do have some places in code when the API gives 200 but without any data (bad APIs). Hate having to write the error handler twice in this case to handle cases when the API returned a response or if it failed due to network error. Slowly getting those APIs upgraded to reflect the appropriate status.