Promise error handling

A Promise is an object representing the eventual completion or failure of an asynchronous operation. Since most people are consumers of already-created promises, this guide will explain consumption of returned promises before explaining how to create them.
  • « Previous
  • Next »

A Promise is an object representing the eventual completion or failure of an asynchronous operation. Since most people are consumers of already-created promises, this guide will explain consumption of returned promises before explaining how to create them.

Essentially, a promise is a returned object to which you attach callbacks, instead of passing callbacks into a function. Imagine a function, createAudioFileAsync(), which asynchronously generates a sound file given a configuration record and two callback functions: one called if the audio file is successfully created, and the other called if an error occurs.

Here’s some code that uses createAudioFileAsync():

function successCallback(result) {
  console.log(`Audio file ready at URL: ${result}`);
}

function failureCallback(error) {
  console.error(`Error generating audio file: ${error}`);
}

createAudioFileAsync(audioSettings, successCallback, failureCallback);

If createAudioFileAsync() were rewritten to return a promise, you would attach your callbacks to it instead:

createAudioFileAsync(audioSettings).then(successCallback, failureCallback);

This convention has several advantages. We will explore each one.

Chaining

A common need is to execute two or more asynchronous operations back to back, where each subsequent operation starts when the previous operation succeeds, with the result from the previous step. In the old days, doing several asynchronous operations in a row would lead to the classic callback pyramid of doom:

doSomething(function (result) {
  doSomethingElse(result, function (newResult) {
    doThirdThing(newResult, function (finalResult) {
      console.log(`Got the final result: ${finalResult}`);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

With promises, we accomplish this by creating a promise chain. The API design of promises makes this great, because callbacks are attached to the returned promise object, instead of being passed into a function.

Here’s the magic: the then() function returns a new promise, different from the original:

const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);

This second promise (promise2) represents the completion not just of doSomething(), but also of the successCallback or failureCallback you passed in — which can be other asynchronous functions returning a promise. When that’s the case, any callbacks added to promise2 get queued behind the promise returned by either successCallback or failureCallback.

With this pattern, you can create longer chains of processing, where each promise represents the completion of one asynchronous step in the chain. In addition, the arguments to then are optional, and catch(failureCallback) is short for then(null, failureCallback) — so if your error handling code is the same for all steps, you can attach it to the end of the chain:

doSomething()
  .then(function (result) {
    return doSomethingElse(result);
  })
  .then(function (newResult) {
    return doThirdThing(newResult);
  })
  .then(function (finalResult) {
    console.log(`Got the final result: ${finalResult}`);
  })
  .catch(failureCallback);

You might see this expressed with arrow functions instead:

doSomething()
  .then((result) => doSomethingElse(result))
  .then((newResult) => doThirdThing(newResult))
  .then((finalResult) => {
    console.log(`Got the final result: ${finalResult}`);
  })
  .catch(failureCallback);

Important: Always return results, otherwise callbacks won’t catch the result of a previous promise (with arrow functions, () => x is short for () => { return x; }). If the previous handler started a promise but did not return it, there’s no way to track its settlement anymore, and the promise is said to be «floating».

doSomething()
  .then((url) => {
    // I forgot to return this
    fetch(url);
  })
  .then((result) => {
    // result is undefined, because nothing is returned from
    // the previous handler.
    // There's no way to know the return value of the fetch()
    // call anymore, or whether it succeeded at all.
  });

This may be worse if you have race conditions — if the promise from the last handler is not returned, the next then handler will be called early, and any value it reads may be incomplete.

const listOfIngredients = [];

doSomething()
  .then((url) => {
    // I forgot to return this
    fetch(url)
      .then((res) => res.json())
      .then((data) => {
        listOfIngredients.push(data);
      });
  })
  .then(() => {
    console.log(listOfIngredients);
    // Always [], because the fetch request hasn't completed yet.
  });

Therefore, as a rule of thumb, whenever your operation encounters a promise, return it and defer its handling to the next then handler.

const listOfIngredients = [];

doSomething()
  .then((url) =>
    fetch(url)
      .then((res) => res.json())
      .then((data) => {
        listOfIngredients.push(data);
      }),
  )
  .then(() => {
    console.log(listOfIngredients);
  });

// OR

doSomething()
  .then((url) => fetch(url))
  .then((res) => res.json())
  .then((data) => {
    listOfIngredients.push(data);
  })
  .then(() => {
    console.log(listOfIngredients);
  });

Nesting

In the two examples above, the first one has one promise chain nested in the return value of another then() handler, while the second one uses an entirely flat chain. Simple promise chains are best kept flat without nesting, as nesting can be a result of careless composition. See common mistakes.

Nesting is a control structure to limit the scope of catch statements. Specifically, a nested catch only catches failures in its scope and below, not errors higher up in the chain outside the nested scope. When used correctly, this gives greater precision in error recovery:

doSomethingCritical()
  .then((result) =>
    doSomethingOptional(result)
      .then((optionalResult) => doSomethingExtraNice(optionalResult))
      .catch((e) => {}),
  ) // Ignore if optional stuff fails; proceed.
  .then(() => moreCriticalStuff())
  .catch((e) => console.error(`Critical failure: ${e.message}`));

Note that the optional steps here are nested — with the nesting caused not by the indentation, but by the placement of the outer ( and ) parentheses around the steps.

The inner error-silencing catch handler only catches failures from doSomethingOptional() and doSomethingExtraNice(), after which the code resumes with moreCriticalStuff(). Importantly, if doSomethingCritical() fails, its error is caught by the final (outer) catch only, and does not get swallowed by the inner catch handler.

Chaining after a catch

It’s possible to chain after a failure, i.e. a catch, which is useful to accomplish new actions even after an action failed in the chain. Read the following example:

new Promise((resolve, reject) => {
  console.log("Initial");

  resolve();
})
  .then(() => {
    throw new Error("Something failed");

    console.log("Do this");
  })
  .catch(() => {
    console.error("Do that");
  })
  .then(() => {
    console.log("Do this, no matter what happened before");
  });

This will output the following text:

Initial
Do that
Do this, no matter what happened before

Note: The text «Do this» is not displayed because the «Something failed» error caused a rejection.

Common mistakes

Here are some common mistakes to watch out for when composing promise chains. Several of these mistakes manifest in the following example:

// Bad example! Spot 3 mistakes!

doSomething()
  .then(function (result) {
    // Forgot to return promise from inner chain + unnecessary nesting
    doSomethingElse(result).then((newResult) => doThirdThing(newResult));
  })
  .then(() => doFourthThing());
// Forgot to terminate chain with a catch!

The first mistake is to not chain things together properly. This happens when we create a new promise but forget to return it. As a consequence, the chain is broken — or rather, we have two independent chains racing. This means doFourthThing() won’t wait for doSomethingElse() or doThirdThing() to finish, and will run concurrently with them — which is likely unintended. Separate chains also have separate error handling, leading to uncaught errors.

The second mistake is to nest unnecessarily, enabling the first mistake. Nesting also limits the scope of inner error handlers, which—if unintended—can lead to uncaught errors. A variant of this is the promise constructor anti-pattern, which combines nesting with redundant use of the promise constructor to wrap code that already uses promises.

The third mistake is forgetting to terminate chains with catch. Unterminated promise chains lead to uncaught promise rejections in most browsers. See error handling below.

A good rule of thumb is to always either return or terminate promise chains, and as soon as you get a new promise, return it immediately, to flatten things:

doSomething()
  .then(function (result) {
    // If using a full function expression: return the promise
    return doSomethingElse(result);
  })
  // If using arrow functions: omit the braces and implicitly return the result
  .then((newResult) => doThirdThing(newResult))
  // Even if the previous chained promise returns a result, the next one
  // doesn't necessarily have to use it. You can pass a handler that doesn't
  // consume any result.
  .then((/* result ignored */) => doFourthThing())
  // Always end the promise chain with a catch handler to avoid any
  // unhandled rejections!
  .catch((error) => console.error(error));

Note that () => x is short for () => { return x; }.

Now we have a single deterministic chain with proper error handling.

Using async/await addresses most, if not all of these problems — the tradeoff being that it may be easy to forget the await keyword.

Error handling

You might recall seeing failureCallback three times in the pyramid of doom earlier, compared to only once at the end of the promise chain:

doSomething()
  .then((result) => doSomethingElse(result))
  .then((newResult) => doThirdThing(newResult))
  .then((finalResult) => console.log(`Got the final result: ${finalResult}`))
  .catch(failureCallback);

If there’s an exception, the browser will look down the chain for .catch() handlers or onRejected. This is very much modeled after how synchronous code works:

try {
  const result = syncDoSomething();
  const newResult = syncDoSomethingElse(result);
  const finalResult = syncDoThirdThing(newResult);
  console.log(`Got the final result: ${finalResult}`);
} catch (error) {
  failureCallback(error);
}

This symmetry with asynchronous code culminates in the async/await syntax:

async function foo() {
  try {
    const result = await doSomething();
    const newResult = await doSomethingElse(result);
    const finalResult = await doThirdThing(newResult);
    console.log(`Got the final result: ${finalResult}`);
  } catch (error) {
    failureCallback(error);
  }
}

It builds on promises — for example, doSomething() is the same function as before, so there’s minimal refactoring needed to change from promises to async/await. You can read more about the async/await syntax in the async functions and await references.

Promises solve a fundamental flaw with the callback pyramid of doom, by catching all errors, even thrown exceptions and programming errors. This is essential for functional composition of asynchronous operations.

Promise rejection events

If a promise rejection event is not handled by any handler, it bubbles to the top of the call stack, and the host needs to surface it. On the web, whenever a promise is rejected, one of two events is sent to the global scope (generally, this is either the window or, if being used in a web worker, it’s the Worker or other worker-based interface). The two events are:

unhandledrejection

Sent when a promise is rejected but there is no rejection handler available.

rejectionhandled

Sent when a handler is attached to a rejected promise that has already caused an unhandledrejection event.

In both cases, the event (of type PromiseRejectionEvent) has as members a promise property indicating the promise that was rejected, and a reason property that provides the reason given for the promise to be rejected.

These make it possible to offer fallback error handling for promises, as well as to help debug issues with your promise management. These handlers are global per context, so all errors will go to the same event handlers, regardless of source.

In Node.js, handling promise rejection is slightly different. You capture unhandled rejections by adding a handler for the Node.js unhandledRejection event (notice the difference in capitalization of the name), like this:

process.on("unhandledRejection", (reason, promise) => {
  // Add code here to examine the "promise" and "reason" values
});

For Node.js, to prevent the error from being logged to the console (the default action that would otherwise occur), adding that process.on() listener is all that’s necessary; there’s no need for an equivalent of the browser runtime’s preventDefault() method.

However, if you add that process.on listener but don’t also have code within it to handle rejected promises, they will just be dropped on the floor and silently ignored. So ideally, you should add code within that listener to examine each rejected promise and make sure it was not caused by an actual code bug.

Composition

There are four composition tools for running asynchronous operations concurrently: Promise.all(), Promise.allSettled(), Promise.any(), and Promise.race().

We can start operations at the same time and wait for them all to finish like this:

Promise.all([func1(), func2(), func3()]).then(([result1, result2, result3]) => {
  // use result1, result2 and result3
});

If one of the promises in the array rejects, Promise.all() immediately rejects the returned promise and aborts the other operations. This may cause unexpected state or behavior. Promise.allSettled() is another composition tool that ensures all operations are complete before resolving.

These methods all run promises concurrently — a sequence of promises are started simultaneously and do not wait for each other. Sequential composition is possible using some clever JavaScript:

[func1, func2, func3]
  .reduce((p, f) => p.then(f), Promise.resolve())
  .then((result3) => {
    /* use result3 */
  });

In this example, we reduce an array of asynchronous functions down to a promise chain. The code above is equivalent to:

Promise.resolve()
  .then(func1)
  .then(func2)
  .then(func3)
  .then((result3) => {
    /* use result3 */
  });

This can be made into a reusable compose function, which is common in functional programming:

const applyAsync = (acc, val) => acc.then(val);
const composeAsync =
  (...funcs) =>
  (x) =>
    funcs.reduce(applyAsync, Promise.resolve(x));

The composeAsync() function accepts any number of functions as arguments and returns a new function that accepts an initial value to be passed through the composition pipeline:

const transformData = composeAsync(func1, func2, func3);
const result3 = transformData(data);

Sequential composition can also be done more succinctly with async/await:

let result;
for (const f of [func1, func2, func3]) {
  result = await f(result);
}
/* use last result (i.e. result3) */

However, before you compose promises sequentially, consider if it’s really necessary — it’s always better to run promises concurrently so that they don’t unnecessarily block each other unless one promise’s execution depends on another’s result.

Creating a Promise around an old callback API

A Promise can be created from scratch using its constructor. This should be needed only to wrap old APIs.

In an ideal world, all asynchronous functions would already return promises. Unfortunately, some APIs still expect success and/or failure callbacks to be passed in the old way. The most obvious example is the setTimeout() function:

setTimeout(() => saySomething("10 seconds passed"), 10 * 1000);

Mixing old-style callbacks and promises is problematic. If saySomething() fails or contains a programming error, nothing catches it. This is intrinsic to the design of setTimeout.

Luckily we can wrap setTimeout in a promise. The best practice is to wrap the callback-accepting functions at the lowest possible level, and then never call them directly again:

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

wait(10 * 1000)
  .then(() => saySomething("10 seconds"))
  .catch(failureCallback);

The promise constructor takes an executor function that lets us resolve or reject a promise manually. Since setTimeout() doesn’t really fail, we left out reject in this case. For more information on how the executor function works, see the Promise() reference.

Timing

Lastly, we will look into the more technical details, about when the registered callbacks get called.

Guarantees

In the callback-based API, when and how the callback gets called depends on the API implementor. For example, the callback may be called synchronously or asynchronously:

function doSomething(callback) {
  if (Math.random() > 0.5) {
    callback();
  } else {
    setTimeout(() => callback(), 1000);
  }
}

This leads to the state of Zalgo, because it makes side effects hard to analyze:

let value = 1;
doSomething(() => {
  value = 2;
});
console.log(value); // 1 or 2?

On the other hand, promises are a form of inversion of control — the API implementor does not control when the callback gets called. Instead, the job of maintaining the callback queue and deciding when to call the callbacks is delegated to the promise implementation, and both the API user and API developer automatically gets strong semantic guarantees, including:

  • Callbacks added with then() will never be invoked before the completion of the current run of the JavaScript event loop.
  • These callbacks will be invoked even if they were added after the success or failure of the asynchronous operation that the promise represents.
  • Multiple callbacks may be added by calling then() several times. They will be invoked one after another, in the order in which they were inserted.

To avoid surprises, functions passed to then() will never be called synchronously, even with an already-resolved promise:

Promise.resolve().then(() => console.log(2));
console.log(1);
// Logs: 1, 2

Instead of running immediately, the passed-in function is put on a microtask queue, which means it runs later (only after the function which created it exits, and when the JavaScript execution stack is empty), just before control is returned to the event loop; i.e. pretty soon:

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

wait(0).then(() => console.log(4));
Promise.resolve()
  .then(() => console.log(2))
  .then(() => console.log(3));
console.log(1); // 1, 2, 3, 4

Task queues vs. microtasks

Promise callbacks are handled as a microtask whereas setTimeout() callbacks are handled as task queues.

const promise = new Promise((resolve, reject) => {
  console.log("Promise callback");
  resolve();
}).then((result) => {
  console.log("Promise callback (.then)");
});

setTimeout(() => {
  console.log("event-loop cycle: Promise (fulfilled)", promise);
}, 0);

console.log("Promise (pending)", promise);

The code above will output:

Promise callback
Promise (pending) Promise {<pending>}
Promise callback (.then)
event-loop cycle: Promise (fulfilled) Promise {<fulfilled>}

For more details, refer to Tasks vs. microtasks.

When promises and tasks collide

If you run into situations in which you have promises and tasks (such as events or callbacks) which are firing in unpredictable orders, it’s possible you may benefit from using a microtask to check status or balance out your promises when promises are created conditionally.

If you think microtasks may help solve this problem, see the microtask guide to learn more about how to use queueMicrotask() to enqueue a function as a microtask.

See also

Summary: in this tutorial, you will learn how to deal with error handling in promises.

Suppose that you have a function called getUserById() that returns a Promise:

function getUserById(id) { return new Promise((resolve, reject) => { resolve({ id: id, username: 'admin' }); }); }

Code language: JavaScript (javascript)

Normal error

First, change the getUserById() function to throw an error outside the promise:

function getUserById(id) { if (typeof id !== 'number' || id <= 0) { throw new Error('Invalid id argument'); } return new Promise((resolve, reject) => { resolve({ id: id, username: 'admin' }); }); }

Code language: JavaScript (javascript)

Second, handle the promise by using both then() and catch() methods:

getUserById('a') .then(user => console.log(user.username)) .catch(err => console.log(err));

Code language: JavaScript (javascript)

The code throws an error:

Uncaught Error: Invalid id argument

Code language: JavaScript (javascript)

When you raise an exception outside the promise, you must catch it with try/catch:

try { getUserById('a') .then(user => console.log(user.username)) .catch(err => console.log(`Caught by .catch ${error}`)); } catch (error) { console.log(`Caught by try/catch ${error}`); }

Code language: JavaScript (javascript)

Output:

Caught by try/catch Error: Invalid id argument

Code language: JavaScript (javascript)

Errors inside the Promises

We change the getUserById() function to throw an error inside the promise:

let authorized = false; function getUserById(id) { return new Promise((resolve, reject) => { if (!authorized) { throw new Error('Unauthorized access to the user data'); } resolve({ id: id, username: 'admin' }); }); }

Code language: JavaScript (javascript)

And consume the promise:

try { getUserById(10) .then(user => console.log(user.username)) .catch(err => console.log(`Caught by .catch ${error}`)); } catch (error) { console.log(`Caught by try/catch ${error}`); }

Code language: JavaScript (javascript)

Output:

Caught by .catch Error: Unauthorized access to the user data

Code language: JavaScript (javascript)

If you throw an error inside the promise, the catch() method will catch it, not the try/catch.

If you chain promises, the catch() method will catch errors that occurred in any promise. For example:

promise1 .then(promise2) .then(promise3) .then(promise4) .catch(err => console.log(err));

Code language: JavaScript (javascript)

In this example, if any error in the promise1, promise2, or promise4, the catch() method will handle it.

Calling reject() function

Throwing an error has the same effect as calling the reject() as illustrated in the following example:

let authorized = false; function getUserById(id) { return new Promise((resolve, reject) => { if (!authorized) { reject('Unauthorized access to the user data'); } resolve({ id: id, username: 'admin' }); }); } try { getUserById(10) .then(user => console.log(user.username)) .catch(err => console.log(`Caught by .catch ${err}`)); } catch (error) { console.log(`Caught by try/catch ${error}`); }

Code language: JavaScript (javascript)

In this example, instead of throwing an error inside the promise, we called the reject() explicitly. The catch() method also handles the error in this case.

Missing the catch() method

The following example does not provide the catch() method to handle the error inside the promise. It will cause a runtime error and terminate the program:

function getUserById(id) { return new Promise((resolve, reject) => { if (!authorized) { reject('Unauthorized access to the user data'); } resolve({ id: id, username: 'admin' }); }); } try { getUserById(10) .then(user => console.log(user.username)); // the following code will not execute console.log('next'); } catch (error) { console.log(`Caught by try/catch ${error}`); }

Code language: JavaScript (javascript)

Output:

Uncaught (in promise) Unauthorized access to the user data

If the promise is resolved, you can omit the catch() method. In the future, a potential error may cause the program to stop unexpectedly.

Summary

  • Inside the promise, the catch() method will catch the error caused by the throw statement and reject().
  • If an error occurs and you don’t have the catch() method, the JavaScript engine issues a runtime error and stops the program.

Was this tutorial helpful ?

JavaScript promises allow asynchronous code to use structured error handling. The promise chains are served as a great way of error handling.

Whenever a promise rejects, the control jumps to the nearest rejection handler.
One of the most useful methods of error-handling is .catch.

As it was already mentioned, .catch is one of the most useful methods for error handling in JavaScript.

Let’s view a case, in which the URL to fetch is wrong (no such site), and the .catch method handles it:

w3docs logo
Javascript promise fetch is wrong and .catch method

fetch(‘https://noSuchServer.someText’) // rejects
.then(response => response.json())
.catch(err => console.log(err)) // TypeError: failed to fetch (the text may vary)

But note that .catch is not immediate. It might appear after one or several .then.

Everything might be correct with the site, but the response is not valid JSON.
The simplest way of catching all the errors is to append .catch to the end of the chain. Here is an example:

w3docs logo
Javascript promise fetch is wrong and .catch method

fetch(‘/promiseChaining/user.json’)
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(user => new Promise((resolve, reject) => {
let img = document.createElement(‘img’);
img.src = user.avatarUrl;
img.className = «promiseAvatarExample»;
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(user);
}, 3000);
}))
.catch(error => console.log(error.message));

Usually, .catch doesn’t trigger. But, in case any of the promises, as mentioned earlier, rejects, it will catch it.

There is an invisible try..catch around the code of a promise handler and promise executor. In case of an exception, it will be treated as a rejection.

It is shown in the following code:

new Promise((resolve, reject) => {
  throw new Error("Error!!");
}).catch(console.log); // Error: Error!!

It operates the same as this code:

w3docs logo
Javascript try..catch

new Promise((resolve, reject) => {
reject(new Error(«Error!!»));
}).catch(console.log); // Error: Error!!

The invisible try..catch will catch the error and transform it into a rejected promise. It can happen both in the executor function and its handlers. In case you throw inside a .then handler, it means a rejected promise, and the control jumps to the closest error handler.

An example will look like this:

w3docs logo
Javascript try..catch

new Promise((resolve, reject) => {
resolve(«Yes»);
}).then((result) => {
throw new Error(«Error!!»); // rejects the promise
}).catch(console.log); // Error: Error!!

That may happen to all the errors, not just ones caused by the throw statement.

We can consider a programming error as an example:

w3docs logo
Javascript throw statement

new Promise((resolve, reject) => {
resolve(«Yes»);
}).then((result) => {
someFunction(); // no such function
}).catch(console.log); // ReferenceError: someFunction is not defined

The final .catch will catch both explicit rejections and accidental errors in the handlers.

As it was already stated, .catch at the end of the promise chain is equivalent to try..catch. You may have as many .then handlers as you like, then use a single .catch at the end of the chain for handling all the errors.

The regulartry..catch allows you to analyze an error and rethrow it if it can’t be handled. A similar situation is possible for promises.

If you throw inside .catch, then the control will go to the next nearest error handler. If you handle the error and finish normally, it will continue to the next nearest successful .then handler.

The example below illustrates the successful error-handling with .catch:

w3docs logo
Javascript error-handling with .catch

// the execution: catch -> then
new Promise((resolve, reject) => {
throw new Error(«Error!!»);
}).catch(function (error) {
console.log(«The error-handling, continue normally»);
}).then(() => console.log(«The next successful handler runs»));

In the example above, the .catch block finishes normally. Hence, the next effective .then is called.

Now let’s check out another situation with .catch. The handler (*) catches the error but is not capable of handling it:

w3docs logo
Javascript error-handling with .catch console.log

// the execution: catch -> catch -> then
new Promise((resolve, reject) => {
throw new Error(«Error!!»);
}).catch(function (error) { // (*)
if (error instanceof URIError) {
// handle
} else {
console.log(«Can’t handle such a error»);
throw error; // throwing this or that error jumps to the next catch
}
}).then(function () {
/* doesn’t run here */
}).catch(error => { // (**)
console.log(`The unknown error: ${error}`);
// do not return anything => execution goes the usual way
});

The execution jumps from the initial .catch (*) to the following one down the chain.

In this section, we will examine the cases when errors are not handled.

Let’s see that you have forgotten to append .catch to the end of the chain.

Here is an example:

new Promise(function () {
    noSuchFunc(); // Error here, no such function
  })
  .then(() => {
    // successful promise handlers
  }); // no append .catch at the end

If there is an error, the promise will be rejected. The execution will jump to the nearest rejection handler. But there exists none, and the error will get “stuck”.There isn’t any code for handling it.

So, what will happen if an error is not caught by try..catch? The script will collapse with a console message. Things like that occur with unhandled promise rejections.

The engine of JavaScript usually tracks this kind of rejections, generating a global error.

For catching such errors in the browser, you can use the event unhandledRejection, as follows:

window.addEventListener('unhandledRejection', function (event) {
  // the event object has two special properties
  console.log(event.promise); // [object Promise] - error
  console.log(event.reason); // Error: Error!! - the unhandled error
});
new Promise(function () {
  throw new Error("Error!!");
}); // no catch to handle the error

So, in case there is an error, and no .catch can be found, the unhandledRejection will trigger getting the event object with the information regarding the error.

As a rule, this kind of errors are unrecoverable. The most proper solution in such circumstances is to inform the user about it, reporting the incident to the server.

Non-browser environments, such as Node.js, include other options for tracking unhandled errors.

One of the most significant assets of using promises is the way they allow you to handle errors.

Errors in the promises can be handled with .catch: no matter it’s a reject() call or an error thrown in a handler. It would be best if you put .catch precisely in the places where you want to handle errors. The handler analyzes the errors rethrowing the ones that are unknown (for example, programming mistakes).

In any other case, you need to have unhandledRejection event handler ( for browsers and analogs of different environments). It will track unhandled errors informing the user about them. It will help you avoid the collapse of your app.

Introduction

One of the biggest concerns of complex applications, be they for web or desktop, is to handle errors effectively.

This requires the use of the conventional try..catch statements bundled with the throw keyword; in addition to listening for error events, laying out numerous if checks in the code and much more on this way.

Sometimes it also requires passing in callbacks to be fired on the occurrence of any errors. When this is the case and the overall code is quite involved, then using a callback simply means to be writing code that will ultimately lead to unmanageable clutter — as discussed in detail in the Promises Introduction chapter.

What’s rather a better alternative is to use a promise!

In this chapter we shall introduce you to the catch() method inherited by all promises and see how it’s analogous to the second argument to then(), and how to use all these features to handle bugs occurring in the wrapped-up asynchronous operation.

Let’s begin!

What is error handling?

We’ll start by answering the question — what is error handling — using our all-time favourite example i.e AJAX.

Suppose you make a request to some random file on your server using XMLHttpRequest() — the core of AJAX. What errors do you think can possibly occur in the whole request-response cycle?

Do this as a quick exercise — it will give you a good warmup on the topic!

To name a few:

  1. The request can be made to a non-existent file in which case the server would send a 404, Not Found, status code.
  2. The server script at the backend might have an invalid syntax in which case it would response with a 500 Internal Server Error, status.
  3. The client’s network could be down in which case the error event would be dispatched.
  4. The request can violate the CORS policy in which case, once again, the error event will be fired.

and so on….

Actually, it depends on the application itself — for example it can be configured to parse a JSON response and then read a property on the parsed object to determine the status of the response.

Whatever the case be, the main point over here is that errors can happen in any asynchronous operation and thereby it’s imperative for us to write code that handles them effectively.

How to handle errors? Well it’s pretty elementary!

Error handling in JavaScript is usually done with two things: events and conditional statements.

Events such as error and abort frequently fire, even in simplistic applications; likewise it’s common to provide onerror and onabort handlers to eventually respond to each case.

Similarly, often times one needs to lay out conditional checks in order target errors — for example by checking the status property of an XMLHttpRequest() object against the value 200 in the load event, we can throw an exception once we know it’s not equal to 200.

As another example: we can check for the availability of the XMLHttpRequest() API and consequently throw an exception if it’s not suported.

So now that we know how to handle errors in programming, it’s finally time that we dive right into implementing it in promises.

Rejection callback

Recall the then() method and the callback arguments we provide to it: the first one is the onFulfilled callback which fires once the promise is fulfilled while the second one is the onRejected callback which fires once the promise is rejected.

If onRejected is provided; well and good, but if it’s not, then the default "Thrower" argument is taken to be the callback.

The question is that when does a given promise get rejected!

Well there are two ways to reject a promise: one is by invoking the reject() callback passed to the executor whereas the other is by throwing an exception explicitly inside the executor, using the throw keyword.

The way the latter works is described as follows:

When the Promise() constructor is instantiated, it immediately executes the provided executor function, inside a try block — much like the code below:

function Promise(executor) {
    // invoke the executor function
    try { executor(); }
    catch(e) { reject(e); }
}

The corresponding catch block calls the same reject() function passed to the executor, and supplies it with the error argument e, as shown above.

This means that any thrown exceptions inside the executor will cause the corresponding promise to be rejected with the thrown value.

Let’s consider a couple of examples.

Following is the code to illustrate promise rejection, done by explicitly calling the reject() argument:

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        reject("Sorry");
    }, 3000);
});

Here if we log the promise object p after 3 seconds, we’ll get something similar to the following:

Promise {<rejected>: «Sorry»}

If we want we can also pass in a callback to then() to handle the promise’s rejection after 3 seconds. This is shown below:

p.then(null, function(error) {
    console.log("An error occurred: " + error);
});

The second argument here is an anonymous function that logs an error message in the console, when invoked roughly after 3 seconds.

Recall that it’s the second argument to then() that deals with errors; NOT the first one, which in this case is set to null (it isn’t required for the example and so there’s no point of giving one).

Now for the second case — throwing an exception explicitly within the promise — consider the code below:

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        throw "Sorry";
    }, 3000);
});

Although it’s different syntactically, this code does exactly the same thing as the one shown above with reject(). When the statement throw "Sorry" is executed, control flow shifts to the internal catch block, which then calls the reject() function with the throw value "Sorry".

Once again, we can attach a failure callback to the promise p here, which’ll operate exactly the same as in the previous example.

p.then(null, function(error) {
    console.log("An error occurred: " + error);
});

Moving on, as we know from the previous chapter on Promise Chaining, then() returns a promise which depends on the respective passed-in callback to complete.

In the occasion where it throws an error itself, the returned promise is rejected with the thrown value.

Consider the code below:

var p = new Promise(function(resolve, reject) {
    resolve("OK");
});

var p2 = p.then(function(data) {
    throw "Sorry";
});

console.log(p2);

Promise {<rejected>: «Sorry»}

See how the main promise p is resolved but the one derived by calling then() i.e p2, is rejected — simply because the callback throws an error.

A derived promise is rejected with value v, if an exception v is thrown in the corresponding then()‘s callback.

On the same lines, we can also return a promise in the callback that gets rejected eventually and thus causes the derived promise to get rejected as well:

var p = new Promise(function(resolve, reject) {
    resolve("OK");
});

var p2 = p.then(function(data) {
    // return a rejected promise
    return new Promise(function(resolve, reject) {
        reject("Sorry");
    });
});

If we log p2 after a while over here, we’ll get something similar to the following:

Promise {<rejected>: «Sorry»}

This happens because when a then() callback returns a promise itself, the corresponding derived promise mimics that returned promise — in this case p2 mimics the promise in line 7.

Now retreating back to the scenario where an explicit exception is thrown inside the callback for then(), we have an amazing concept following from this very basic idea, which we’re about to explore next.

First of all it’s vital for us to understand that if we don’t provide a failure callback to then(), the method will default it to the "Thrower" function — which will simply throw an exception with the value of the promise.

Consider the code below:

var p = new Promise(function(resolve, reject) {
    reject("Oops!");
});

p.then(null, function(error) {
    throw error;
});

This is the same as writing the following (with the second argument to then() omitted this time):

var p = new Promise(function(resolve, reject) {
    reject("Oops!");
});

p.then(null);

For more info on the default arguments to then(), please read Promises Basics.

With this understood, try to solve the task below and see how well have you learnt promises overall!

What log will be made in the console by the following code? Explain your answer.

Conventional catching

The method then() both works and sounds well in putting up a fulfillment callback on a promise object.

However when it comes to the failure callback, it doesn’t look that meaningful, especially in cases where multiple async operations are to be handled by a single callback.

For instance, consider the code below:

var p = new Promise(function(resolve, reject) {
    someAsyncOperation();
}).
then(function(data) {
    someOtherAsyncOperation();
}).
then(function(data) {
    someOtherAsyncOperation2();
}).
then(null, function(error) {
    alert("An error occurred: " + error);
});

Notice how the last then() method serves the job of handling any errors occurring in the upstream chain of promises, but doesn’t right away seem to do so.

Does the last then() call, in the code above, anyhow seem to be handling errors? Does it visually convey that message to you?

The reason why this happens is that we’re not fairly used to identifier names like then to serve the purpose of catching any thrown exceptions. This purpose is well served by the conventional name ‘catch’.

Moreover, if you notice closely you’ll see that when our task is to only handle errors, then() regardless requires us to pass in two arguments — one could be null or undefined while the second has to be error-handling function.

In other words, then() enforces us to mention at least two arguments even in error-handling cases.

Hence, what all this led to was the team of developers of ES6 giving a method catch() to promises. Let’s discuss on it!

For a given function f(),

catch(f) is exactly synonymous with then(null, f).

The method catch() is nothing new; just a spoon of syntactic sugar over the powerful then() method!

Following we demonstrate a couple of examples using this method.

Here we are using catch() to handle a very basic promise rejection.

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        reject("Sorry");
    }, 3000);
});

p.catch(function(error) {
    console.log("An error occurred: " + error);
});

catch() only accepts one argument i.e the onRejected() callback to fire on the main promise’s rejection.

In contrast, below we use the method in a much more useful way — the one illustrated at the start of this section i.e handling errors in a chain of promises:

var p = new Promise(function(resolve, reject) {
    someAsyncOperation();
}).
then(function(data) {
    someOtherAsyncOperation();
}).
then(function(data) {
    someOtherAsyncOperation2();
}).
catch(function(error) {
    alert("An error occurred: " + error);
});

See how the last catch() call only requires us to pass in one argument i.e the error-handling callback, as opposed to passing an additional null value to then().

In conclusion

At this point you shall be well versed with a couple of things such as how catch() works internally, how it can handle errors occurring anywhere in an entire promise chain and so on.

You shall also appreciate the fact that it’s not the magic of catch() to be able to error-handle chains of promises; instead it’s the magic of the "Thrower" function that throws errors and thus gets them to travel downstream into the catch() method!

In short, error handling is crucial to programming and similarly crucial to promises in JavaScript. While callbacks work well syntactically in dealing with simple errors, as soon as the underlying asynchronous operation becomes more complicated they start to lose this essence.

The best solution for complex use cases is, therefore, to use promises instead and the techniques for handling errors inside them.

dbigpot

dbigpot

Posted on Oct 11, 2019

• Updated on Oct 16, 2019

Error handling in JavaScript can be easy while being tricky at certain places, especially Promises. JS allows error handling with the help of try, catch, and throw.

const main = () => {
  try {
    // Do something super crazy
    if (!bakePizza()) {
      throw new Error('Oh no!');
    }
  } catch (error) {
    // That escalated quickly
    handleOvenExplosion();
  }
}

This seems simple enough but gets a little tricky when Promises are involved.

Let’s look at a simple example of a Promise. The following Promise function fetches a list of user profiles from the database, where the result set is resolved by the promise function and the error is rejected.

const userProfileQuery = new Promise((resolve, reject) => {
  connection.query('SELECT * FROM Users', [], (err, result) => {
    if (err) reject({ type: 'SQL', err});
    connection.release();
    resolve(result);
});
userProfileQuery
  .then((data) => {
    const userList = data;
    // Do something crazy with the list
  })
  .catch((err) => {
    // Oh, snap!
    // Handle error
  });

In an ideal world, we’d want to have a single try-catch block to handle all errors that occur in that single file.

const { getUserProfiles } = require('./helpers');
module.exports = () => {
  try {
    let userProfileList;
    getUserProfiles
      .then((data) => {
        userProfileList = data;
      })
      .catch((error) => {
        // Handle Promise Error
        // All errors thrown in this promise land here
      });
  } catch (error) {
    // Handle errors in this module
  }
}

The above module is simple — It fetches a list of user profiles with the help of a Promise function.

But the problem with the above module is that when we throw a new Error inside the then block of the promise, it will always pass to the catch block of the promise. That is because throwing a new error inside a then block of a promise will always be passed to the catch block of the invoking promise function. This does not allow us to handle all errors in a module with a singular try-catch block.

But, alas! There is a way to handle this with the help of Async/Await. Let me explain this better with an example —

const { getUserProfiles } = require('./helpers');
module.exports = async () => {
  try {
    const userProfileList = await getUserProfiles;
  } catch (error) {
    // Handle errors in this module
    switch (type) {
      case ERROR_SQL:
        // Handle SQL errors
      default:
        // Handle common errors
    }
  }
}

This little addition of async/await in your code does two things —
Assign the value to the variable which was resolved by the promise function.

Throw error if the promise function rejects anything.
Note that the value assignment works only when a promise functions resolves some value and errors get thrown only when the promise function rejects something.

This way, async/await lets us keep our code clean, maintainable, and easy to read.


Thanks for reading. If you have thoughts on this, be sure to leave a comment.

Back to: JavaScript Tutorial For Beginners and Professionals

JavaScript Promise Error Handling with Examples

In this article, I am going to discuss JavaScript Promise Error Handling with Examples. Please read our previous article where we discussed JavaScript Promise.race() vs Promise.all() in detail. 

JavaScript Promise Error Handling

Promise chains are good at error handling. When a promise rejects, the control jumps to the closest rejection function. Errors thrown from promises are handled by the second parameter (reject) passed to then() or by the handler passed to catch()

.then(null, error => { /* handle error here */ });
// or
foo().catch(error => { /* handle error here */ });

Synchronously throwing an error from function that should return a promise

Imagine a function below like this:

<html>
<head>
    <title>JavaScript Promise error Handling example</title>
</head>
<body>
    <script>
        function foo(arg) {
            if (typeof arg != 'number' || arg <= 0) {
                throw new Error('Invalid arg argument')
            }
            return new Promise((resolve, reject) =>
                setTimeout(() =>
                    resolve(arg + ' ' + 'completed'), 1000)
            )
        }

        foo('fdfd')
            .then(result => console.log(result))
            .catch(err => console.log(err)) // <-- Error: Invalid argument will be caught here
    </script>
</body>
</html>

Output:

JavaScript Promise Error Handling with Examples

In the above example foo() function throw an error outside the promise, which gets handled by using both then() and catch() methods.

Raise an exception outside the promise

When we raise an exception outside the promise, we must catch it with try/catch.

<html>
<head>
    <title>JavaScript Promise error handling with try catch example</title>
</head>
<body>
    <script>
        function foo(arg) {
            if (typeof arg != 'number' || arg <= 0) {
                throw new Error('Invalid arg argument')
            }
            return new Promise((resolve, reject) =>
                setTimeout(() =>
                    resolve(arg + ' ' + 'completed'), 1000)
            )
        }

        try {
            foo('alphnumberic')
                .then(result => console.log(result))
                .catch(err => console.log(`Caught by .catch ${error}`));
        } catch (error) {
            console.log(`Caught by try/catch ${error}`);
        }
    </script>
</body>
</html>

Output:

Raise an exception outside the promise

Throw an error inside the Promises

Inside the promise, the catch() method will catch the error caused by the throw statement and reject(). We have modified the existing function so as to throw an error inside the promises and then consume the promise.

<html>
<head>
    <title>JavaScript throw an error inside the Promises example</title>
</head>
<body>
    <script>
        let isValid = false;
        function foo(arg) {
            return new Promise((resolve, reject) => {
                if (!isValid) {
                    throw new Error('Invalid argument is passed to function')
                }
                setTimeout(() =>
                    resolve(arg + ' ' + 'completed'), 1000)
            })
        }

        try {
            foo('alphnumberic')
                .then(result => console.log(result))
                .catch(error => console.log(`Caught by .catch ${error}`));
        } catch (error) {
            console.log(`Caught by try/catch ${error}`);
        }
    </script>
</body>
</html>

Output:

Throw an error inside the Promises

If we throw an error inside the promises the catch() method will catch the error, not by the try/catch.

Calling reject() method of Promise inside the promise

reject() method returns a promise that is rejected. It is highly used for debugging purposes and error catching. We have modified the existing function so as to throw an error by calling reject() method. As throwing an error has the same impact as calling the reject() method that has elaborated in the below example:

<html>
<head>
    <title>JavaScript throw an error by calling reject() method example</title>
</head>
<body>
    <script>
        let isValid = false;
        function foo(arg) {
            return new Promise((resolve, reject) => {
                if (!isValid) {
                    reject('Invalid argument is passed to function')
                }
                setTimeout(() =>
                    resolve(arg + ' ' + 'completed'), 1000)
            })
        }

        try {
            foo('alphnumberic')
                .then(result => console.log(result))
                .catch(error => console.log(`Caught by .catch ${error}`));
        } catch (error) {
            console.log(`Caught by try/catch ${error}`);
        }
    </script>
</body>
</html>

Output:

Calling reject() method of Promise inside the promise

In the above example rather than throwing an error inside the promise, we have called the reject() method of promise explicitly. In this case catch() method also handles the error as it handles in throwing an error inside the promises.

Unhandled rejected by missing the catch() method

An error will be silently ignored if a promise doesn’t have a catch() block or reject() handler. If any error occurs and we don’t have the catch() method, the JavaScript engine issues a runtime error and stops the execution of the program.

<html>
<head>
    <title>JavaScript missing the catch()method example</title>
</head>
<body>
    <script>
        let isValid = false;
        function foo(arg) {
            return new Promise((resolve, reject) => {
                if (!isValid) {
                    reject('Invalid argument is passed to function')
                }
                setTimeout(() =>
                    resolve(arg + ' ' + 'completed'), 1000)
            })
        }

        try {
            foo('alphnumberic')
                .then(result => console.log(result))//this will not execute
                //.catch(error => console.log(`Caught by .catch ${error}`));
        } catch (error) {
            console.log(`Caught by try/catch ${error}`);
        }
    </script>
</body>
</html>

Output:

Unhandled rejected by missing the catch() method

Chaining a Promise

If we have a promise chain then an error occurred in any promises, the catch() method will catch it.

<html>
<head>
    <title>JavaScript error handing chaining a promise example</title>
</head>
<body>
    <script>
        function foo(arg) {
            return new Promise((resolve, reject) => {
                if (typeof arg === 'undefined') {
                    throw new Error('Invalid argument is passed to function')
                }
                setTimeout(() =>
                    resolve(arg + ' ' + 'completed'), 1000)
            })
        }

        foo('dsds') //promise1
            .then(result => foo())  //promise2
            .then(result => foo('dsds')) //promise3
            .then(result => foo('dsds')) //promise4
            .catch(error => console.log(`Caught by .catch ${error}`));
    </script>
</body>
</html>

Output:

JavaScript Promise Error Handling with Examples

In the above example if any error occurred in Promise1, promise2, promise3, or promise4 then it will catch by catch() method.

In the next article, I am going to discuss JavaScript Async Await with Examples. Here, in this article, I try to explain the JavaScript Promise Error Handling with Examples. I hope this JavaScript Promise Error Handling with Examples article will help you with your need. I would like to have your feedback. Please post your feedback, question, or comments about this JavaScript Promise Error Handling.

Понравилась статья? Поделить с друзьями:
  • Prototype 2 mainwin32 error
  • Prototype 2 error could not load prototype2engine dll
  • Proton vpn network error linux
  • Protocol error with rcmd
  • Promag 50 ошибка 321