Skip to content
On this page

Asynchronous Error Handling

When writing JavaScript code, we must always remember that the JavaScript engine is an event-driven execution engine that runs in a single-threaded manner. Callback functions are executed only when the next suitable event occurs.

For example, the setTimeout() function can take a callback function and execute it after a specified number of milliseconds:

javascript
function printTime() {
    console.log('It is time!');
}

setTimeout(printTime, 1000);
console.log('done');

The code above will print "done" first, and "It is time!" will be printed after one second.

If an error occurs within the printTime() function, wrapping setTimeout() in a try block is ineffective:

javascript
function printTime() {
    throw new Error();
}

try {
    setTimeout(printTime, 1000);
    console.log('done');
} catch (e) {
    console.log('error');
}

The reason is that when the setTimeout() function is called, the printTime function does not execute immediately! Instead, the JavaScript engine continues to execute the console.log('done'); statement, and at that point, no error has occurred. The error occurs only when the printTime function is executed one second later, but by then, the outer code cannot catch it.

Thus, when dealing with asynchronous code, we cannot catch errors at the time of invocation because the callback function has not yet executed.

Similarly, when we handle an event, we cannot catch errors in the event handler at the point where the event is bound.

For example, consider the following form:

html
<form>
    <input id="x"> + <input id="y">
    <button id="calc" type="button">Calculate</button>
</form>

We bind a click event to the button with the following code:

javascript
let btn = document.querySelector('#calc');

// Clear any previously bound events:
btn.onclick = null;

try {
    btn.onclick = function () {
        let
            x = parseFloat(document.querySelector('#x').value),
            y = parseFloat(document.querySelector('#y').value),
            r;
        if (isNaN(x) || isNaN(y)) {
            throw new Error('Invalid input');
        }
        r = x + y;
        alert('Calculation result: ' + r);
    };
} catch (e) {
    alert('Invalid input!');
}

However, when the user inputs invalid values, the error is not caught by the handler. To fix the error handling code, we can wrap the logic inside the event handler in a try block. Here’s the corrected version:

javascript
let btn = document.querySelector('#calc');

// Clear any previously bound events:
btn.onclick = null;

btn.onclick = function () {
    try {
        let
            x = parseFloat(document.querySelector('#x').value),
            y = parseFloat(document.querySelector('#y').value),
            r;
        if (isNaN(x) || isNaN(y)) {
            throw new Error('Invalid input');
        }
        r = x + y;
        alert('Calculation result: ' + r);
    } catch (e) {
        alert('An error occurred: ' + e.message);
    }
};

In this updated code, any errors that occur during the execution of the click event handler will be caught and handled properly, providing feedback to the user.

Asynchronous Error Handling has loaded