try-catch doesn’t catch an error thrown in timer

JavaScript/TypeScript

try-catch doesn’t catch an error thrown in timer friend functions setTimeout, setInterval and setImmediate.

Using timer function alone

This code doesn’t work. As you can see, its process dies.

try {
    global.setTimeout(() => {
        console.log("internal process1 working...");
        throw new Error("throw an error1");
    }, 1000);
} catch (e) {
    console.log(e.message);
}

// internal process1 working...

// ...\src\simple-code\timers\app.ts:5
//         throw new Error("throw an error1");
//               ^
// Error: throw an error1
//     at Timeout._onTimeout (...\src\simple-code\timers\app.ts:5:15)
//     at listOnTimeout (internal/timers.js:554:17)
//     at processTimers (internal/timers.js:497:7)
// npm ERR! code ELIFECYCLE
// npm ERR! errno 1
// npm ERR! [email protected] start-timer: `ts-node ./timers/app.ts`
// npm ERR! Exit status 1
// npm ERR!
// npm ERR! Failed at the [email protected] start-timer script.
// npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

// npm ERR! A complete log of this run can be found in:
// npm ERR!

It’s because the callback for the timer friend function isn’t in the same context when it’s triggered. The callback is pushed into a queue and called there.
There are two ways to solve this problem. First one is to define uncaughtException event callback if it is running on Node.js.

process.on("uncaughtException", (e) => {
    console.log(e.message) 
});

// internal process1 working...
// throw an error1

However, this code catches all uncaught Exceptions. It is not handy.
Second way is to define try-catch in the timer function.

global.setTimeout(() => {
    try {
        console.log("internal process2 working...");
        throw new Error("throw an error2");
    } catch (e) {
        console.error(e.message);
    }
}, 1000);
//internal process2 working...
// throw an error2

Timer function with Promise

You use timer function with Promise if you want to do something after its callback process finishes. Following code doesn’t work.

const promise = new Promise((_, reject) => {
    global.setTimeout(() => {
        console.log("internal process3 working...");
        throw new Error("throw an error3");
    }, 1000);
});
promise.catch((e) => {
    console.log(`error caught in promise.catch.`);
    console.log(e.message);
});
// internal process3 working...

// ...\src\simple-code\timers\app.ts:22
//             throw new Error("throw an error3");
//                   ^
// Error: throw an error3
//     at Timeout._onTimeout (...\src\simple-code\timers\app.ts:22:19)
//     at listOnTimeout (internal/timers.js:554:17)
//     at processTimers (internal/timers.js:497:7)
// npm ERR! code ELIFECYCLE
// npm ERR! errno 1
// npm ERR! [email protected] start-timer: `ts-node ./timers/app.ts`
// npm ERR! Exit status 1
// npm ERR!
// npm ERR! Failed at the [email protected] start-timer script.
// npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

// npm ERR! A complete log of this run can be found in:
// npm ERR!

Call reject function instead when it throws an error.

const promise = new Promise((_, reject) => {
    global.setTimeout(() => {
        try {
            console.log("internal process4 working...");
            throw new Error("throw an error4");
        } catch (e) {
            reject(e);
        }
    }, 1000);
});
promise.catch((e) => {
    console.log(`error caught in promise.catch.`);
    console.log(e.message);
});
// internal process4 working...
// error caught in promise.catch.
// throw an error4

Summary

Enclose your code with try-catch in the timer friend function.
Call reject function if you call it in Promise.

Comments

Copied title and URL