Welcome to ciysys blog

Error handling for async/await and Promise

Published on: 30th Apr 2022

Overview

In the previous blog (Async/await vs Promise and its side effects), we did not explain how to handle the runtime error.

Let's do some testing

The async and Promise is helpful on the process that involves I/O processes such as database calls or network calls.

For any I/O processes, there will be a chance that the connection to the given resource is not accessible for the reason of lost connection, resource is locked, deadlock, etc. As a result, the I/O process will fail.

  1. What will happen if there is an error that occurred when executing query_price() without catching the error.

    Let's say you have a function that retrieves the price for a given product from the database. There is a chance that this database call might fail due to the database server not being accessible, the table has been locked (due to deadlock), etc.

    function query_price(prod_id) {
        return new Promise(function (resolve, reject) {
            reject('Not able to get the price');
        });
    }
    
    (async function () {
        let price = await query_price(101);
        
        // continue processing the price
    })();
    

    Executing the above code will return the following error message.

    internal/process/promises.js:213
            triggerUncaughtException(err, true /* fromPromise */);
            ^
    
    [UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "Not able to get the price".] {
    code: 'ERR_UNHANDLED_REJECTION'
    }
    

    Resolution: to avoid your program from crashing, you have to catch the error like this

    (async function () {
        try {
            await query_price();
        }
        catch (x) {
            console.log('caught an error: ' + x);
        }
    })();
    
  2. Assuming that you have two levels of calling: query_prod() calls query_price().

    If query_prod() called query_price() and query_prod() does not catch the error raised by query_price(), the whole process will crash.

    function query_prod() {
        return new Promise(function (resolve, reject) {
            query_price()
                .then(function () {
                    resolve();
                });
        });
    }
    
    function query_price() {
        return new Promise(function (resolve, reject) {
            reject('Not able to get the price');
        });
    }
    
    (async function () {
        try {
            await query_prod();
        }
        catch (x) {
            console.log(x);
        }
    })();
    

    Executing the above code will return the following error message.

    internal/process/promises.js:213
            triggerUncaughtException(err, true /* fromPromise */);
            ^
    
    [UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "Not able to get the price".] {
    code: 'ERR_UNHANDLED_REJECTION'
    }
    

    Resolution: to prevent your program from crashing, query_prod() must catch the error internal.

    function query_prod() {
        return new Promise(function (resolve, reject) {
            query_price()
                .then(function () {
                    resolve();
                })
                .catch(function (x2) {
                    reject(x2);  //<<==========handle the error here
                });
        });
    }
    
  3. If we chain the calls using Promise style without handling the Promise .catch() where query_price2() and query_qty_on_hand() are independent, it will crash.

    // let's create a new function that will succeed.
    function query_price2() {
        return new Promise(function (resolve, reject) {
            resolve();
        });
    }
    
    // but, the query for quantity on hand will fail.
    function query_qty_on_hand() {
        return new Promise(function (resolve, reject) {
            reject('Not able to get the qty on hand');
        });
    }
    
    query_price2()
        .then(function () {
            query_qty_on_hand()
                .then(function () {
                    console.log('got the price & qty on hand');
                });
    
                //<<==== it will crash because '.catch()' is missing.
        })
        .catch(function (x) {
            console.log('caught an error: ' + x);
        });
    

    The result is,

    internal/process/promises.js:213
            triggerUncaughtException(err, true /* fromPromise */);
            ^
    
    [UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "Not able to get the qty on hand".] {
    code: 'ERR_UNHANDLED_REJECTION'
    }
    

    Resolution: add a .catch() block after query_qty_on_hand().then().

    query_price2()
        .then(function () {
            query_qty_on_hand()
                .then(function () {
                    console.log('got the price & qty on hand');
                })
                .catch(function (x2) {
                    console.log('caught an error: ' + x2);  //<<==========caught the error raise in query_qty_on_hand()
                });
        })
        .catch(function (x) {
            console.log('caught an error: ' + x);
        });
    

    Resolution #2: with a bit of modification to the calls to query_qty_on_hand(), we will be able to catch the error.

    query_price2()
        .then(function () {
            return query_qty_on_hand(); //<<==========returns the Promise
        })
        .then(function () {
            console.log('got the price & qty on hand');
        })
        .catch(function (x) {
            console.log('caught an error: ' + x);  //<<==========caught the error raise in query_qty_on_hand()
        });
    

    Or do it in async code,

    (async function () {
        try {
            await query_price2();
            await query_qty_on_hand();
        } catch (x) {
            console.log('caught an error: ' + x);
        }
    })();
    

    The result is,

    caught an error: Not able to get the qty on hand
    
  4. Here's another situation where both query_price() and query_qty_on_hand() will throw an error.

    function query_price() {
        return new Promise(function (resolve, reject) {
            reject('Not able to get the price');
        });
    }
    
    function query_qty_on_hand() {
        return new Promise(function (resolve, reject) {
            reject('Not able to get the qty on hand');
        });
    }
    
    query_price()
        .then(function () {
            return query_qty_on_hand();
        })
        .then(function () {
            console.log('got the price & qty on hand');
        })
        .catch(function (x) {
            console.log('caught an error: ' + x);
        });
    

    The error has been handled and the result is,

    caught an error: Not able to get the price
    

    This is because query_price() returns an error and the next two .then() function will be skipped and it will be handled by .catch().

    You may use async keyword to achieve the same result instead of Promise .then() and .catch().

    (async function () {
        try {
            await query_price();
            await query_qty_on_hand();
        } catch (x) {
            console.log('caught an error: ' + x);
        }
    })();
    

Conclusion

In JavaScript, the calls to any async function and Promise must handle the error or rejection. Without proper error handling, your program might crash and be difficult to troubleshoot.

Jump to #JAVASCRIPT blog

Author

Lau Hon Wan, software developer.