626

I have read several articles on this subject, but it is still not clear to me if there is a difference between Promise.reject vs. throwing an error. For example,

Using Promise.reject

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            return Promise.reject(new PermissionDenied());
        }
    });

Using throw

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            throw new PermissionDenied();
        }
    });

My preference is to use throw simply because it is shorter, but was wondering if there is any advantage of one over the other.

Naresh
  • 23,937
  • 33
  • 132
  • 204
  • 20
    Both methods produce the exact same response. The `.then()` handler catches the thrown exception and turns it into a rejected promise automatically. Since I've read that thrown exceptions are not particularly fast to execute, I would guess that returning the rejected promise might be slightly faster to execute, but you'd have to devise a test in multiple modern browsers if that was important to know. I personally use `throw` because I like the readability. – jfriend00 Oct 30 '15 at 22:11
  • @webduvet not with Promises - they are designed to work with throw. – joews Oct 30 '15 at 22:16
  • 43
    One downside to `throw` is that it wouldn't result in a rejected promise if it was thrown from within an asynchronous callback, such as a setTimeout. http://jsfiddle.net/m07van33/ @Blondie your answer was correct. – Kevin B Oct 30 '15 at 22:24
  • @joews it doesn't mean it is good ;) – webduvet Oct 30 '15 at 22:27
  • 1
    @KevinB true. I think it is best to replace all async callbacks with Promises for that kind of reason. You can throw from a Promisified timeout: http://jsbin.com/mebogukele/edit?js,console – joews Oct 30 '15 at 22:33
  • 1
    Ah, true. So a clarification to my comment would be, *"if it was thrown from within an asynchronous callback **that wasn't promisified**"*. I knew there was an exception to that, i just couldn't remember what it was. I too prefer to use throw simply because i find it to be more readable, and allows me to omit `reject` it from my param list. – Kevin B Oct 30 '15 at 22:35
  • The symmetry of `return` and `throw` wrt sync/async flow control is one of the neatest things about Promises, IMO. – joews Oct 30 '15 at 22:37
  • One of us should write up an answer, or close as dupe or something. – Kevin B Oct 30 '15 at 22:38
  • I don't know if there is a technical difference beyond what you described, so I have held off from answering. – joews Oct 30 '15 at 22:41
  • i couldn't find a good dupe. – Kevin B Oct 30 '15 at 22:52
  • FYI, here's an attempt at a performance comparison between throw vs. reject. It's harder with async operations to measure so I'm not 100% sure this is a reliable comparison: http://jsperf.com/throw-vs-reject-in-promises/3 – jfriend00 Oct 31 '15 at 04:41
  • Sounds like a duplicate of [Promise.reject vs throw error](http://stackoverflow.com/q/28703241/1048572) (but is not) – Bergi Oct 31 '15 at 12:01
  • 4
    Some of the answers seem to really be misunderstanding what the OP is asking. They are asking about the static method, `Promise.reject`, not the `reject` callback parameter that we commonly name that way. – Ruan Mendes Jul 30 '21 at 20:27
  • See also [How to reject a promise from inside then function](https://stackoverflow.com/q/21260602/1048572) – Bergi Aug 14 '21 at 06:25
  • `Promise.reject` worked well in our case rather than throwing an error in a GraphQL context. – darul75 Nov 25 '21 at 13:46

7 Answers7

532

There is no advantage of using one vs the other, but, there is a specific case where throw won't work. However, those cases can be fixed.

Any time you are inside of a promise callback, you can use throw. However, if you're in any other asynchronous callback, you must use reject.

For example, this won't trigger the catch:

new Promise(function() {
  setTimeout(function() {
    throw 'or nah';
    // return Promise.reject('or nah'); also won't work
  }, 1000);
}).catch(function(e) {
  console.log(e); // doesn't happen
});

Instead you're left with an unresolved promise and an uncaught exception. That is a case where you would want to instead use reject. However, you could fix this in two ways.

  1. by using the original Promise's reject function inside the timeout:

new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('or nah');
  }, 1000);
}).catch(function(e) {
  console.log(e); // works!
});
  1. by promisifying the timeout:

function timeout(duration) { // Thanks joews
  return new Promise(function(resolve) {
    setTimeout(resolve, duration);
  });
}

timeout(1000).then(function() {
  throw 'worky!';
  // return Promise.reject('worky'); also works
}).catch(function(e) {
  console.log(e); // 'worky!'
});
Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
Kevin B
  • 94,570
  • 16
  • 163
  • 180
  • 73
    Worth mentioning that the places inside a non-promisified async callback that you can't use `throw error`, you also can't use `return Promise.reject(err)` which is what the OP was asking us to compare. This is basically why you should not put async callbacks inside of promises. Promisify everything that's async and then you don't have these restrictions. – jfriend00 Oct 30 '15 at 23:00
  • @jfriend00 like that? Editing from phone. – Kevin B Oct 30 '15 at 23:04
  • @KevinB and all - thanks for an excellent discussion/commentary. Per your advice, I will keep thing simple by promisifying everything and using a `throw` for errors. – Naresh Oct 31 '15 at 01:41
  • 13
    "However, if you're in any other kind of callback" really should be "However, if you're in any other kind of _asynchronous_ callback". Callbacks can be synchronous (e.g. with `Array#forEach`) and with those, throwing inside them would work. – Félix Saparelli Feb 16 '17 at 23:25
  • @KevinB, do you think this answer needs a bit of cleanup especially in reference to what the OP asked? – Anshul May 01 '17 at 13:43
  • 3
    @KevinB reading these lines "there is a specific case where throw won't work." and "Any time you are inside of a promise callback, you can use throw. However, if you're in any other asynchronous callback, you must use reject." I get a feeling that the example snippets will show cases where `throw` will not work and instead `Promise.reject` is a better choice. However the snippets are unaffected with any of those two choices and give same result irrespective of what you choose. Am I missing something? – Anshul May 02 '17 at 13:25
  • @Anshul Looking at my first snippet, it correctly fails to catch the error, and in the second snippet it correctly catches it. I don't see the problem. – Kevin B May 02 '17 at 18:52
  • 1
    @KevinB Yeah, but those snippets have exactly same behaviour irrespective of whether you throw or return a Promise.reject. I can't see the difference :( – Anshul May 03 '17 at 03:43
  • In the first, there's an uncaught error. In the other, there's no error, because i caught it (in the console it's just logged, not errored) – Kevin B May 03 '17 at 03:44
  • 2
    @KevinB What I meant was, for any of the snippets, it doesn't matter whether you are using `throw` or `Promise.reject`, you get exactly same behavior. For example Snippet 1, which doesn't catch the error, will not catch it irrespective of whether you used `throw 'or nah'` or did `return Promise.reject('or nah')`. – Anshul May 03 '17 at 13:31
  • right, that was kinda the point. there's no significant difference. – Kevin B May 03 '17 at 14:12
  • 1
    @KevinB I am still confused. From your answer "Any time you are inside of a promise callback, you can use throw. However, if you're in any other asynchronous callback, you must use reject." - does this statement still hold true? I don't see any difference whether you use throw or use reject. – Anshul May 05 '17 at 03:21
  • 3
    yes. if you use throw in a setTimeout, the catch will not be called. you must use the `reject` that was passed to the `new Promise(fn)` callback. – Kevin B May 05 '17 at 03:54
  • 2
    @KevinB Ohk. Now I see the source of confusion. I have been reading all the examples with `Promise.reject()` which the OP asked however the emphasis in your answer is on using the `reject` passed to the `Promise` constructor. – Anshul May 05 '17 at 04:06
  • the op never had a new Promise() constructed promise, so they never ahd a "reject" callback to call. So in their specific case, it didn't matter because all of their methods were promisified. – Kevin B May 05 '17 at 04:07
  • but i couldn't say there was no difference ever, because that's not true. So i provided a scenario where they wouldn't be the same. – Kevin B May 05 '17 at 04:11
  • 2
    @KevinB thanks for staying along. The example given by OP mentions he specifically wanted to compare `return Promise.reject()` and `throw`. He doesn't mention the `reject` callback given in `new Promise(function(resolve, reject))` construct. So while your two snippets rightly demonstrate when you should be using the resolve callback, OP's question wasn't that. – Anshul May 05 '17 at 11:09
  • that's what the first 10 words of my answer was about. the examples then show that this is also true in cases where neither will work. – Kevin B May 05 '17 at 14:35
  • @KevinB this answer is confusing. You cannot use either `throw` or `Promise.reject` in an asynchronous callback. I think you mean `reject` in a promise executor. – Alexander Mills May 03 '18 at 22:06
  • meh, i don't get what you're saying – Kevin B May 04 '18 at 02:03
  • I guess you never tried this, because throwing error from "then" works only in Chromium based browsers. I just sent a Firefox bug report. It does not work in Edge and Safari either. – inf3rno Oct 29 '18 at 09:57
  • @inf3rno I dunno what you're talking about. I get identical results from the above two tests in firefox and chrome, latest versions. The first one results in an uncaught error, the second results in non-error logged text. – Kevin B Oct 29 '18 at 15:45
  • 1
    @KevinB Yes, right, it works because you caught the error by "then". Try this instead: `Promise.resolve().then(function (){throw "x";});`. – inf3rno Oct 30 '18 at 07:24
  • This should not be the correct answer, the suggested answer doesn't even call `Promise.reject` It did not help me understand if there was any reason I should use return `Promise.reject()` over `throw` if I'm in a then handler – Ruan Mendes Jun 08 '21 at 17:47
  • @JuanMendes *"There is no advantage of using one vs the other"* use the method that best fits your usecase. – Kevin B Jun 08 '21 at 17:50
  • @KevinB That statement is highly misleading, you say one (`Promise.reject`) vs the other (`throw`) but then your suggestion is a third approach (Promise constructor resolve callback), which is [typically considered an anti-pattern](https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it). – Ruan Mendes Jun 08 '21 at 17:56
  • 1
    @JuanMendes i did not suggest any approach, i pointed out a scenario that worked slightly differently. I did not indicate that approach was good or bad/otherwise, as that wasn't the intent of brining it up. – Kevin B Jun 08 '21 at 17:57
  • 1
    I guess Kevin's mentioning of this separate issue and taking up 90% of the answer got me and @Anshul confused :p I should be OK with it, [one of my highest rated answers](https://stackoverflow.com/questions/51185/are-javascript-strings-immutable-do-i-need-a-string-builder-in-javascript/4717855#4717855) is also not really about the original question . I did add a little more clarification about the original question years later. – Ruan Mendes Jul 30 '21 at 20:35
288

Another important fact is that reject() DOES NOT terminate control flow like a return statement does. In contrast throw does terminate control flow.

Example:

new Promise((resolve, reject) => {
  throw "err";
  console.log("NEVER REACHED");
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

vs

new Promise((resolve, reject) => {
  reject(); // resolve() behaves similarly
  console.log("ALWAYS REACHED"); // "REJECTED" will print AFTER this
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));
Adam Tuttle
  • 19,505
  • 17
  • 80
  • 113
lukyer
  • 7,595
  • 3
  • 37
  • 31
  • 64
    Well the point is correct but the comparison is tricky. Because normally you should return your rejected promise by writing `return reject()`, so the next line won't run. – AZ. Jan 26 '17 at 23:45
  • 12
    Why would you want to return it? – lukyer Jan 27 '17 at 16:07
  • 44
    In this case, `return reject()` is simply a shorthand for `reject(); return` i.e. what you want is to terminate flow. The return value of the _executor_ (the function passed to `new Promise`) is not used, so this is safe. – Félix Saparelli Feb 16 '17 at 23:19
  • 1
    This one tripped me up for a while. Is there any good reason that `reject()` does not terminate flow? It seems like it should. – 223seneca Mar 01 '21 at 20:32
  • 3
    @223seneca reject is just a normal javascript function like any other, so it cannot terminate flow because functions in general should not be able to terminate their caller. – Katharsas Mar 25 '21 at 12:57
77

Yes, the biggest difference is that reject is a callback function that gets carried out after the promise is rejected, whereas throw cannot be used asynchronously. If you chose to use reject, your code will continue to run normally in asynchronous fashion whereas throw will prioritize completing the resolver function (this function will run immediately).

An example I've seen that helped clarify the issue for me was that you could set a Timeout function with reject, for example:

new Promise((resolve, reject) => {
  setTimeout(()=>{reject('err msg');console.log('finished')}, 1000);
  return resolve('ret val')
})
.then((o) => console.log("RESOLVED", o))
.catch((o) => console.log("REJECTED", o));

The above could would not be possible to write with throw.

try{
  new Promise((resolve, reject) => {
    setTimeout(()=>{throw new Error('err msg')}, 1000);
    return resolve('ret val')
  })
  .then((o) => console.log("RESOLVED", o))
  .catch((o) => console.log("REJECTED", o));
}catch(o){
  console.log("IGNORED", o)
}

In the OP's small example the difference in indistinguishable but when dealing with more complicated asynchronous concept the difference between the two can be drastic.

Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
Blondie
  • 833
  • 5
  • 6
  • 5
    This sounds like a key concept, but I don't understand it as written. Still too new to Promises, I guess. – David Spector Dec 22 '18 at 12:14
  • 4
    @DavidSpector - No, I'm really deeply familiar with promises and I'm struggling to understand what's being explained above, too. :-) Unless it's talking about the same thing [Kevin B](https://stackoverflow.com/a/33446005/157247) posted a little bit after the above. Certainly the stuff about "prioritizing" something is unclear. Blondie, do you want to clarify? – T.J. Crowder Aug 12 '20 at 08:41
  • This is not correct. throw new Error("o_O") is the same as reject(new Error("o_O")). Reference https://learn-javascript-ru.translate.goog/promise?_x_tr_sl=ru&_x_tr_tl=en&_x_tr_hl=en-US&_x_tr_pto=nui – Ren Nov 09 '21 at 10:27
  • 2
    The OP is **NOT** asking about the Promise constructor. He is asking about throwing an error **inside a .then()**. There's two ways to throw an error inside a .then() - using `throw` or `return Promise.reject()`. **BOTH WORK SYNCHRONOUSLY** – slebetman Dec 22 '21 at 05:12
  • @slebetman I think his example used a `then`, but I don't think it has to. My understanding is that if an error bubbles up from an asynchronous callback, `throw` will cause the error to be handled even if a `resolve` is called before, but `reject` will ignore it. Make sure you are using the `reject` from the outside Promise, not the inside one. – Alaska Nov 26 '22 at 00:30
60

TLDR: A function is hard to use when it sometimes returns a promise and sometimes throws an exception. When writing an async function, prefer to signal failure by returning a rejected promise

Your particular example obfuscates some important distinctions between them:

Because you are error handling inside a promise chain, thrown exceptions get automatically converted to rejected promises. This may explain why they seem to be interchangeable - they are not.

Consider the situation below:

checkCredentials = () => {
    let idToken = localStorage.getItem('some token');
    if ( idToken ) {
      return fetch(`https://someValidateEndpoint`, {
        headers: {
          Authorization: `Bearer ${idToken}`
        }
      })
    } else {
      throw new Error('No Token Found In Local Storage')
    }
  }

This would be an anti-pattern because you would then need to support both async and sync error cases. It might look something like:

try {
  function onFulfilled() { ... do the rest of your logic }
  function onRejected() { // handle async failure - like network timeout }
  checkCredentials(x).then(onFulfilled, onRejected);
} catch (e) {
  // Error('No Token Found In Local Storage')
  // handle synchronous failure
} 

Not good and here is exactly where Promise.reject ( available in the global scope ) comes to the rescue and effectively differentiates itself from throw. The refactor now becomes:

checkCredentials = () => {
  let idToken = localStorage.getItem('some_token');
  if (!idToken) {
    return Promise.reject('No Token Found In Local Storage')
  }
  return fetch(`https://someValidateEndpoint`, {
    headers: {
      Authorization: `Bearer ${idToken}`
    }
  })
}

This now lets you use just one catch() for network failures and the synchronous error check for lack of tokens:

checkCredentials()
      .catch((error) => if ( error == 'No Token' ) {
      // do no token modal
      } else if ( error === 400 ) {
      // do not authorized modal. etc.
      }
maxwell
  • 2,313
  • 23
  • 35
  • 5
    Op's example always returns a promise, however. The question is referring to whether you should use `Promise.reject` or `throw` when you want to return a rejected promise (a promise that will jump to the next `.catch()`). – Marcos Pereira Oct 19 '17 at 11:50
  • @maxwell - I like you example. In the same time if on the fetch you will add a catch and in it you throw the exception then you will be safe to use try ... catch... There is no perfect world on exception flow, but I think that using one single pattern makes sense, and combining the patterns is not safe (aligned with your pattern vs anti-pattern analogy). –  Nov 06 '17 at 00:12
  • 3
    Excellent answer but I find here a flaw - this pattern assume all errors are handled by returning a Promise.reject - what happens with all the unexpected errors that simply might be thrown from checkCredentials()? – chenop Sep 04 '18 at 13:07
  • 1
    Yeah you're right @chenop - to catch those unexpected errors you would need to wrap in try/catch still – maxwell Sep 04 '18 at 17:26
  • 2
    I don't understand @maxwell's case. Couldn't you just structure it so you do `checkCredentials(x).then(onFulfilled).catch(e) {}`, and have the `catch` handle both the rejection case and the thrown error case? – Ben Wheeler Jul 08 '19 at 15:12
  • I know that OP's question is about Promises, and this answer assumes using `checkCredentials` via Promsies. But I'm wondering if with async/await throwing would be a better choice. – OlehZiniak Jan 13 '21 at 09:30
  • Many many functions throw an error or return a value. Just pretending that you are not throwing an error by doing `return Promise.reject()` will still behave the same way... – Ruan Mendes Jun 08 '21 at 17:49
16

There's one difference — which shouldn't matter — that the other answers haven't touched on, so:

If the fulfillment handler passed to then throws, the promise returned by that call to then is rejected with what was thrown.

If it returns a rejected promise, the promise returned by the call to then is resolved to that promise (and will ultimately be rejected, since the promise it's resolved to is rejected), which may introduce one extra async "tick" (one more loop in the microtask queue, to put it in browser terms).

Any code that relies on that difference is fundamentally broken, though. :-) It shouldn't be that sensitive to the timing of the promise settlement.

Here's an example:

function usingThrow(val) {
    return Promise.resolve(val)
        .then(v => {
            if (v !== 42) {
                throw new Error(`${v} is not 42!`);
            }
            return v;
        });
}
function usingReject(val) {
    return Promise.resolve(val)
        .then(v => {
            if (v !== 42) {
                return Promise.reject(new Error(`${v} is not 42!`));
            }
            return v;
        });
}

// The rejection handler on this chain may be called **after** the
// rejection handler on the following chain
usingReject(1)
.then(v => console.log(v))
.catch(e => console.error("Error from usingReject:", e.message));

// The rejection handler on this chain may be called **before** the
// rejection handler on the preceding chain
usingThrow(2)
.then(v => console.log(v))
.catch(e => console.error("Error from usingThrow:", e.message));

If you run that, as of this writing you get:

Error from usingThrow: 2 is not 42!
Error from usingReject: 1 is not 42!

Note the order.

Compare that to the same chains but both using usingThrow:

function usingThrow(val) {
    return Promise.resolve(val)
        .then(v => {
            if (v !== 42) {
                throw new Error(`${v} is not 42!`);
            }
            return v;
        });
}

usingThrow(1)
.then(v => console.log(v))
.catch(e => console.error("Error from usingThrow:", e.message));

usingThrow(2)
.then(v => console.log(v))
.catch(e => console.error("Error from usingThrow:", e.message));

which shows that the rejection handlers ran in the other order:

Error from usingThrow: 1 is not 42!
Error from usingThrow: 2 is not 42!

I said "may" above because there's been some work in other areas that removed this unnecessary extra tick in other similar situations if all of the promises involved are native promises (not just thenables). (Specifically: In an async function, return await x originally introduced an extra async tick vs. return x while being otherwise identical; ES2020 changed it so that if x is a native promise, the extra tick is removed where there is no other difference.)

Again, any code that's that sensitive to the timing of the settlement of a promise is already broken. So really it doesn't/shouldn't matter.

In practical terms, as other answers have mentioned:

  • As Kevin B pointed out, throw won't work if you're in a callback to some other function you've used within your fulfillment handler — this is the biggie
  • As lukyer pointed out, throw abruptly terminates the function, which can be useful (but you're using return in your example, which does the same thing)
  • As Vencator pointed out, you can't use throw in a conditional expression (? :), at least not for now

Other than that, it's mostly a matter of style/preference, so as with most of those, agree with your team what you'll do (or that you don't care either way), and be consistent.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
8

An example to try out. Just change isVersionThrow to false to use reject instead of throw.

const isVersionThrow = true

class TestClass {
  async testFunction () {
    if (isVersionThrow) {
      console.log('Throw version')
      throw new Error('Fail!')
    } else {
      console.log('Reject version')
      return new Promise((resolve, reject) => {
        reject(new Error('Fail!'))
      })
    }
  }
}

const test = async () => {
  const test = new TestClass()
  try {
    var response = await test.testFunction()
    return response 
  } catch (error) {
    console.log('ERROR RETURNED')
    throw error 
  }  
}

test()
.then(result => {
  console.log('result: ' + result)
})
.catch(error => {
  console.log('error: ' + error)
})
Chris Livdahl
  • 4,662
  • 2
  • 38
  • 33
5

The difference is ternary operator

  • You can use
return condition ? someData : Promise.reject(new Error('not OK'))
  • You can't use
return condition ? someData  : throw new Error('not OK')
Vencator
  • 53
  • 2
  • 7