3

Is it possible to catch asynchronous errors using the ES6 .catch syntax of promises? For example, the following doesn't work (the .catch doesn't catch the error):

new Promise((resolve, reject)=>{
    setTimeout(()=>{throw new Error("uh oh")}, 1);
}).then(number=>{
    console.log("Number: " + number);
}).catch(e=>{
    console.log("Error: " + e);
});

But this synchronous version does:

new Promise((resolve, reject)=>{
    throw new Error("uh oh");
}).then(number=>{
    console.log("Number: " + number);
}).catch(e=>{
    console.log("Error: " + e);
});

Is the only solution to do something like the following, using a try/catch block and rejecting the error in the catch?

new Promise((resolve, reject)=>{
    try {
        setTimeout(()=>{throw new Error("uh oh")}, 1);
    }
    catch(e) {
        reject(e);
    }
}).then(number=>{
    console.log("Number: " + number);
}).catch(e=>{
    console.log("Error: " + e);
});

For the sake of this question, assume the part of the code that is throwing the Error is in another named function, so it doesn't have access to the reject function.

Thanks!!

Edit: Here is a more complete example of what I'd like to do, in JSFiddle.

Christopher Shroba
  • 7,006
  • 8
  • 40
  • 68
  • `assume the part of the code that is throwing the Error is in another named function` - does this (non-existent in your code) function return a Promise? – Jaromanda X Mar 13 '17 at 00:52
  • Yes, the other function returns a promise normally, but because an asynchronous function inside that function is throwing an Error, the entire function is throwing an Error. And I know the first snippet doesn't work just by running it; the desired behavior is that "Error: " should be console.log'ed, but in reality the Error is being propagated out of the chained calls: http://i.imgur.com/J6CyFW9.png – Christopher Shroba Mar 13 '17 at 00:57
  • No, `catch` will not catch the error thrown inside the timeout because it's in a different "thread". –  Mar 13 '17 at 00:58
  • `solution to do something like the following` - that doesn't solve it either, you know ... try/catch does not catch errors in asynchronous code ... – Jaromanda X Mar 13 '17 at 00:58
  • @JaromandaX Oh, good point, my bad. So what do you recommend for rejecting thrown errors in async code? – Christopher Shroba Mar 13 '17 at 01:01
  • so, you want to reject from a function you haven't shown - I would recommend doing it "the usual way" – Jaromanda X Mar 13 '17 at 01:03
  • I'm sorry, I should have just included the other function originally - I mistakenly thought it wasn't important. I've edited my question with a small JSFiddle which includes the other function. – Christopher Shroba Mar 13 '17 at 01:04
  • 1
    I guess it seems like I should just abandon my desire to `throw` the Error and reject it instead...? – Christopher Shroba Mar 13 '17 at 01:06
  • 3
    This might be a useful read: http://stackoverflow.com/questions/33445415/javascript-promises-reject-vs-throw – Evan Trimboli Mar 13 '17 at 01:07

3 Answers3

2

Use resolve(), reject() within Promise constructor. Handle error at either onRejected or .catch().

Note, once error is handled, onFulfilled at chained .then(), if any, should be reached, unless throw is used within onRejected or .catch(), to explicitly pass error to chained .then(_, onRejected) or .catch()

function fn() {
  throw new Error("uh oh")
}

new Promise((resolve, reject) => {
  setTimeout(() => {
    try {
      resolve(fn())
    } catch (e) {
      reject(e)
    }
  }, 1);
}).then(number => {
  console.log("Number: " + number);
}, e => {
  console.log("Error: " + e);
});
guest271314
  • 1
  • 15
  • 104
  • 177
  • May I suggest using `.catch` at the end of the promise chain instead of using the second argument of `.then`? That way, it would catch any errors in the promise chain. `.catch(e => { console.log("Error: " + e); })` – Raphael Rafatpanah Mar 13 '17 at 01:44
  • Not sure what you mean? The error is handled at `onRejected` at `.then(onFulfilled, onRejected)`, if `throw` is not used at `onRejected` chained `.then()` reaches `onFulfilled`. – guest271314 Mar 13 '17 at 02:00
  • I mean something like this: https://jsfiddle.net/persianturtle/jhojwyqd/ If don't think it's better, please let me know. I'm confused why you wouldn't do this. For example, if the promise chain was long in OP's real world use case, I imagine this would be safer. – Raphael Rafatpanah Mar 13 '17 at 02:15
  • @RaphaelRafatpanah What do you mean by "better"? `javascript` at jsfiddle link is same pattern at jsfiddle linked at http://stackoverflow.com/questions/42755042/how-can-i-catch-an-asynchronous-error-using-js-promises/42755300?noredirect=1#comment72627235_42755300, yes? OP can `throw` error at `onRejected` or `.catch()`, or not `throw` error where chained `.then()`, if any, should reach `onFulfilled` at a possible chained `.then()`. The error is handled at `onRejected` or `.catch()`. It depends upon expected result whether or not to `throw` handled error. – guest271314 Mar 13 '17 at 02:24
  • Better as in safer since it would catch any rejections in the entire promise chain. I was just wondering if you had an objection to using `catch`. – Raphael Rafatpanah Mar 13 '17 at 02:28
  • _"Better as in safer"_ ? "safer" against what? _"since it would catch any rejections in the entire promise chain."_ What promise chain? Still not sure what you mean? – guest271314 Mar 13 '17 at 02:30
  • For example if OP added more `then`s. – Raphael Rafatpanah Mar 13 '17 at 02:32
  • @RaphaelRafatpanah _"For example if OP added more `then`s."_ That would not change the Question or result. The author of the application determines whether to explicitly `throw` error and reach potential `.catch()` or `onRejected`; or handle error and reach `onFulfilled` at a chained `.then()`, if any. That is not the question presented at OP. That is an application requirement specific determination which is made by OP. – guest271314 Mar 13 '17 at 02:34
2

There isn't a way to catch an error thrown like your first example. The problem here is that you are using the Explicit Promise Construction Antipattern. You are trying to have the Promise constructor do more than it needs to do.

Instead, you should promisify the smallest amount of asynchronous functionality and build on top of that. In this case, that would involve producing a promise that waits for a certain amount of time before resolving. Most 3rd party promise libraries already have a .delay() method, but it's very easy to create your own:

let delay = duration => new Promise(resolve => setTimeout(resolve, duration));

Then you can build on top of that, and catch the error easily:

let delay = duration => new Promise(resolve => setTimeout(resolve, duration));

delay(1)
  .then(() => {
    throw new Error("uh oh");
  })
  .then(number => {
    console.log("Number: " + number);
  }).catch(e => {
    console.log("Error: " + e);
  });
JLRishe
  • 99,490
  • 19
  • 131
  • 169
0

"For the sake of this question, assume the part of the code that is throwing the Error is in another named function, so it doesn't have access to the reject function." – Christopher Shroba

"does this (non-existent in your code) function return a Promise?" – Jaromanda X

"Yes, the other function returns a promise normally, but because an asynchronous function inside that function is throwing an Error, the entire function is throwing an Error." – Christopher Shroba

Well next time post your code, because your ability to describe the problem with English will never be as good as actual code. By "asynchronous function" do you mean a function that returns a promise? If so ...


It doesn't matter how deep the errors throw in your Promises. Here's an example function three which calls a function two which calls a function one which has potential to throw an Error in the event the JSON is formed poorly. Each step makes a valuable contribution to the final computation, but in the event one throw an error, it will bubble up through the entire chain of Promises.

const one = (json) => new Promise((resolve, reject) => {
  resolve(JSON.parse(json))
})

const two = (json) => one(json).then(data => data.hello)

const three = (json) => two(json).then(hello => hello.toUpperCase())

three('{"hello":"world"}').then(console.log, console.error)
// "WORLD"

three('bad json').then(console.log, console.error)
// Error: unexpected token b in JSON at position 0

Otherwise by "asynchronous function" you mean it's a function that does not return a Promise and maybe uses a continuation instead? In which case, we'll modify one to wrap the async function in a promise, then two and three will work the same. Of importance, I did not use try/catch in any of my Promise functions

// continuation passing style async function
const asyncParse = (json, k) => {
  try {
    k(null, JSON.parse(json))
  }
  catch (err) {
    k(err)
  }
}

// one now wraps asyncParse in a promise
const one = (json) => new Promise((resolve, reject) => {
  asyncParse(json, (err, data) => {
    if (err)
      reject(err)
    else
      resolve(data)
  })
})

// everything below stays the same
const two = (json) => one(json).then(data => data.hello)

const three = (json) => two(json).then(hello => hello.toUpperCase())

three('{"hello":"world"}').then(console.log, console.error)
// "WORLD"

three('bad json').then(console.log, console.error)
// Error: unexpected token b in JSON at position 0
  

Oh, and if you have an a function f which does not function in either of these two ways – ie function that throws an error but doesn't return a promise or send the error to the continuation - you're dealing with a piece of rubbish and the code you write to depend on f will be rubbish too.

Community
  • 1
  • 1
Mulan
  • 129,518
  • 31
  • 228
  • 259