It’s easy to get confused about how errors and catches bubble up through JavaScript Promises, and you might find yourself asking what happens when an error is thrown inside a Promise? I’m a firm believer in learning by doing and just trying out code when trying to answer questions like this.

Also, despite what people say:

a promise is something you can’t break

You can definitely make some mistakes.

Here are a bunch of examples of what happens in different scenarios to help get a sense for what happens when.

1. Normal errors

// a synchronous error thrown outside the promise, raises an exception
// that must be caught with try/catch

function example() {
  throw new Error("test error outside");
  return new Promise((resolve, reject) => {
    resolve(true);
  });
}

try {
  example()
    .then(r => console.log(`.then(${r})`))
    .catch(e => console.error(`.catch(${e})`));
} catch (e) {
  console.error(`try/catch(${e})`);
}

// > Output:
//
// try/catch(Error: test error outside)

2. Errors inside Promises

// an error thrown inside the promise, triggers .catch()

function example() {
  return new Promise((resolve, reject) => {
    throw new Error("test error inside");
    resolve(true);
  });
}

try {
  example()
    .then(r => console.log(`.then(${r})`))
    .catch(e => console.error(`.catch(${e})`));
} catch (e) {
  console.error(`try/catch(${e})`);
}

// > Output:
//
// .catch(Error: test error inside)

3. Calling reject(…)

// explicitly calling reject, triggers .catch()

function example() {
  return new Promise((resolve, reject) => {
    reject("test reject");
  });
}

try {
  example()
    .then(r => console.log(`.then(${r})`))
    .catch(e => console.error(`.catch(${e})`));
} catch (e) {
  console.error(`try/catch(${e})`);
}

// > Output:
//
// .catch(test reject)

4. Not specifying a .catch(…)

// failing to catch a reject means the code will continue to execute
// as if everything was fine, except it prints a warning
//
// in the future it will be a runtime error that terminates the process

function example() {
  return new Promise((resolve, reject) => {
    reject("test reject");
  });
}

try {
  example().then(r => console.log(`.then(${r})`));
} catch (e) {
  console.error(`try/catch(${e})`);
}

// > Output:
//
// (node:25692) UnhandledPromiseRejectionWarning: test reject
// (node:25692) UnhandledPromiseRejectionWarning: Unhandled promise rejection. 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(). (rejection id: 2)
    // (node:25692) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

5. Not specifying a .catch(…) when Promise resolves ok

// the UnhandledPromiseRejectionWarning only triggers when an
// unhandled promise actually occurs. In the example below it
// appears fine, but future hidden errors may be lurking
//

function example() {
  return new Promise((resolve, reject) => {
    resolve("test resolve");
  });
}

try {
  example().then(r => console.log(`.then(${r})`));
} catch (e) {
  console.error(`try/catch(${e})`);
}

// > Output:
//
// .then(test resolve)

Let’s now look at some examples with the es7 async await syntax. Essentially, any function that is defined as async automatically returns a promise where the .then(…) contains its return value or if an error is thrown, then .catch(…) is triggered with the error it experiences.

Furthermore, you can use await inside an async function to automatically resolve the line as a promise. This will evaluate to the value inside the .then(…) of that promise or throw an error with the value inside the .catch(…) of that promise. Likewise, any errors bubble up in the same way that a reject call would, which allows for the same handling of synchronous errors and promise rejections.

6. Async functions and synchronous errors

// all async functions return promises and any errors that
// are thrown automatically trigger their catch method
//

async function example() {
  throw new Error("test error at top of example");
}

try {
  example()
    .then(r => console.log(`.then(${r})`))
    .catch(e => console.log(`.catch(${e})`));
} catch (e) {
  console.error(`try/catch(${e})`);
}

// > Output:
//
// .catch(Error: test error at top of example)

7. Async functions and synchronous errors inside an await

// using await expects and parses the returned promise.
// If the function throws an error outside the promise,
// this gets thrown inside the async function and that
// bubbles up to the catch of its own promise.
//

async function example() {
  return await inner();
}

const inner = () => {
  throw new Error("test error outside promise");
  return new Promise((resolve, reject) => {
    resolve(true);
  });
};

try {
  example()
    .then(r => console.log(`.then(${r})`))
    .catch(e => console.log(`.catch(${e})`));
} catch (e) {
  console.error(`try/catch(${e})`);
}

// > Output:
//
// .catch(Error: test error outside promise)

8. Async functions and errors inside an await’s Promise

// If the promise returned in an await triggers a
// catch, then that also gets thrown as an error inside
// the async function and once again it bubbles up to the
// catch of its own promise.
//

async function example() {
  return await inner();
}

const inner = () => {
  return new Promise((resolve, reject) => {
    throw new Error("test error inside promise");
    resolve(true);
  });
};

try {
  example()
    .then(r => console.log(`.then(${r})`))
    .catch(e => console.log(`.catch(${e})`));
} catch (e) {
  console.error(`try/catch(${e})`);
}

// > Output:
//
// .catch(Error: test error inside promise)

9. Async functions and calling reject(…) inside an await’s Promise

Left as a practice for the reader! (Spoiler alert: it’s the same result as the prior example.)

I promise, things will get better

Some takeaways

  1. Always add a .catch(…) whenever you have a .then(…)—you don’t want an unhandled Promise rejection!
  2. If you expect a function to synchronously throw an error before a Promise, then you’ll want to wrap it in a try-catch, however, you likely don’t want functions to work like this and more likely such an error will be a coding bug that should be raised
  3. Inside async functions, synchronous errors and Promise rejections all bubble up as a Promise rejection in the function’s returned Promise object

Further exploration

  1. Try out chaining multiple .then(…) calls in a row. This avoids indenting ever deeper as you chain more calls. Both .then(…) and .catch(…) return a Promise, whether you return Promise or a regular variable from the from either. See how a Promise rejection will short-circuit a chain of then calls to the next catch. This also can simplify the your handling of Promise rejections to just one .catch(…) with many Promises.

  2. Chain .then(…) calls after a .catch(…) and trigger different paths of execution.

  3. See what happens when you throw an error inside a .catch(…). You’ll get another unhandled Promise rejection!

Thank you for making it to the end! LMK if anything weird happens with the gifs, I just added the paused state and the play buttons using HTML5 canvas.

promise thumb touch

21 December 2017

An abacus in a pile of toys

I was at my brother’s house recently when I noticed an abacus sitting in my niece’s pile of toys. I paused for a moment and realized I had never learned how to actually use one. I think in my childhood, I wrote it off as some antiquated tool for people who didn’t have access to calculators or couldn’t just do the math in their heads.

Given the marvelous world we live in and how we have access to information right at our fingertips at all times, I searched on my phone and came across this video youtube.com/watch?v=wxsS-gmz554.

The video is 6 minutes long, but I paused it a couple times to play around with the abacus in front of me and try out the various operations myself. After about 10 minutes total, I now knew how to do addition, subtraction, multiplication, and division—even division that goes down to decimal points—with an abacus. Not only did I learn these operations, but I immediately gained appreciation for how well of a job it does at getting one to think visually about how the different powers of 10 interact with these types of operations. It’s really cool to think how familiarity with an abacus can aid someone in immediately adding and subtracting large numbers in their own heads. It’s also cool how you can use the top section to keep track of the math while doing multiplication or division.

I came away from this experience as seeing learning the abacus as a great way to teach people how to approach math problems in different ways. This fits well into my own take on math, which is that the journey is more important than the destination. Understanding various ways to think about a problem and why it works is empowering, it builds creativity, and it makes solving any other math problems easier.

Furthermore, this was a simple reminder to be open to different ideas and learning new things. It’s easy to write-off something that you don’t understand, and I think all of us can look back into our pasts and see various things we initially rejected before actually learning or experiencing them.

While I don’t see myself employing an abacus for an application to my job or everyday life, I’m really happy I chanced across one and took the time to learn about this piece of technology that has existed for thousands of years!

Net Neutrality is under attack by the current administration. This post is what I sent. Please take a moment to let the FCC know you care about protecting Title II classification! read full post…
Some quick and dirty code you can copy-paste into your python interpreter to see what SQL queries have been executed. read full post…
Some thoughts on fake news, patriotic correctness, the president-elect & why one voted for him, and issues fundamental to securing a representative democracy. read full post…

Peter Coles

Peter Coles

is a software engineer who lives in NYC, works at Ringly, and blogs here.
More about Peter »

github · soundcloud · @lethys · rss

It’s time to get big money out of politics. Join the kick-started campaign to put government back in the hands of the people. Pledge mayday.us now