Difference between throw an error directly vs using Promise.reject?

In the exercise,
Promises in JavaScript on Exercism
Translation Service in JavaScript on Exercism,
I have used

  if (res.quality < minimumQuality) {
    return Promise.reject(new QualityThresholdNotMet());
  }

whereas @SleeplessByte and many others have used

if (result.quality < minimumQuality) {
          throw new QualityThresholdNotMet()
}

I am not really sure whether there is any difference between the 2 approaches and whether I should favor one over the other.

One related question I had regarding Promise.reject is the difference between just calling Promise.reject vs returning the result of Promise.reject?

Promise.reject(new QualityThresholdNotMet());
vs
return Promise.reject(new QualityThresholdNotMet());

I got tripped once in this exercise when using Promises where I have just used Promise.then without adding a return statement in front of it return Promise.then() - this has been my confusion for a while - though i have read a number of articles on Promises, nothing has given me a picture of when I should use the return statement and when I should omit.

Hi Siraj,

Having just mentored my mom through that exercise, it feels like it should have a bunch of smaller lead-up exercises like the ‘Elyse learns how to array’ set (but I can’t complain too loudly without actually creating such a set… :)

=====

The Promise subsystem calls your .then(your_code) code from a catch block which will convert anything thrown into a rejected Promise containing the thrown data. So the two methods are approximately equivalent.

(I’m not actually sure if it is that way, or the inverse: that if you return a rejected Promise, the caller will throw the rejected Promise’s value.)

Either way, the two paths are unified.

=====

Promise.reject() simply creates a new already-rejected Promise bearing the value you passed. If you are supposed to be returning a rejected Promise, you need to return that value!

Otherwise it is just an ephemeral value you caused to be created, then discarded; not much different from a standalone statement: Math.max(3,4) – it calls that function, then drops the value on the floor. (However, Promises have additional semantics, so you will get some sort of reaction – but it probably will not be delivered to the place you meant to receive it!)

=====

Code which says return somePromise.then(...) is returning a Promise to the caller. If the function you’re returning from is an API function – that is, you are describing its call interface to other people, who will then use it in their code – you would have to tell them it returns a Promise. If they wanted a concrete value, you would have to make it so, e.g. retVal = await somePromise; return retVal (and document your API as returning a value).

In the ‘Translation Service’ exercise you are creating a class with 4 available methods, each of which is defined, by the exercise instructions, as returning a Promise. So that’s what you have to do.

In some parts of the exercise there are places where you might call .then() and not return or save the received value anywhere: if, in your code, you called the Promise constructor. Then you might have a .then((val) => resolve(val)), in which you are calling the resolve function passed to you by the constructor. That’s still a way of handling the value, but you neither stored it in a variable nor returned it.

=====

Boy it’s hard to talk about this stuff without getting longwinded! Sorry.

1 Like

I’m definitely interested in more “prequel” exercises to explain the concept more clearly. I’ll try to work a few out.

The promises exercise was one I wrote before we had any framework or tooling. We didn’t know yet what the test runner would look like – and so, it’s not as refined as some others ;)

I actually wrote another 950 words in the above, before chopping them out as too far afield.

Rather than copy-paste that in, I’m boiling it down to an attempt to describe rather than explain the concepts I was trying to convey. It’s about half as many words, but I think clearer, to someone in a position to perhaps convert them into exercises…

Also these are not necessarily in a rational order! :)

  1. if you want a Promise to resolve to a certain value, you must return that value from your .then() code

  2. if you want a Promise to resolve as rejected to a certain value, you must throw that value [or return an already rejected Promise]

  3. how .then() / .catch() / .finally() chain in terms of values returned

  4. how .then() / .catch() / .finally() chain in terms of rejections returned, or caught along the way

  5. .finally()'s return value is ignored unless it’s a rejected Promise(!)

  6. sketch of how .then() goes about equivalencing a throw vs. a returned rejected Promise

  7. re-emphasize that all of .then() / .catch() / .finally() always return a Promise of some sort

  8. the conventional names used in new Promise((resolve, reject) => ...) are the same names as the static methods Promise.resolve(), Promise.reject() – but have importantly different roles! THIS IS IMPORTANTLY CONFUSING

  9. you don’t have to call your Promise constructor executor’s args resolve and reject, and might be wise not to

  10. what, after all, is the role of your Promise constructor executor? You are promising to eventually call one of the two functions it passed to you, conveying either the value you want the Promise to eventually have, or the rejection message. (Aggravating caveat: you don’t actually have to call either: you could also throw)

  11. specific treament of what Promise.resolve() / .reject() actually do: return to you an object which is an already-resolved Promise; you can store this in a variable, chain it with .then(), return it, (absurdly) await it, whatever

  12. if the arg you pass to Promise.resolve() is itself a Promise, it is returned exactly as-is

  13. if the arg you pass to Promise.reject() is itself a Promise, a new rejected Promise is constructed, with its value being the Promise you passed in(!)

  14. reiterating on the two meanings of resolve and reject: the conventionally named parameters to the function passed to the Promise constructor are to be dynamically called when you want to convey an action. The static methods of the class are to be called when you want a value which is a resolved or rejected Promise

  15. a SEPARATE dimension of confusion is the roles of the two functions passed to new Promise() vs. two functions passed to .then()

  16. Two functions aren’t passed to the constructor; one function, which takes two functions as args is passed

  17. The dual functions involved in the constructor are functions created and owned by the Promise class; your job is to call one of them in the process of forming the outcome of the Promise

  18. The dual functions involved in .then() are provided (and often written) by you; one of them will be called by the class, allowing you to receive the resolution of the Promise you are ‘thenning’

I’ve looked at something like a dozen ‘all about Promises / Promises made easy’ web pages. All the ones I’ve seen just gloss over these confusions, leaving them festering to bite you over and over in the future. JavaScript Promises are really fricking weird in a number of ways. They’re really cool, super useful, but the names and conventions around them are mind bogglingly confusing.

1 Like

I think it may be wiser to help people understand the what to use when with which goal than to really hem about the “weird things”.

And yeah… Vast majority on web content on promises is completely shit :upside_down_face:

I’ll just say that there is nothing in my 18 points which hasn’t bitten me at least once on the way to my moderately OK understanding of Promises.

1 Like

@filbo, thank you very much for the detailed posts - you could have posted your long version as it might have helped me. Thanks for the clear explanation about both Promise.reject vs throwing an Error having the same effect. Your likening Promise.reject to Math.max is illuminating.

@SleeplessByte I agree with you - a task-based approach to help new people understand when to use different flavours of Promise would be helpful. I look forward to having more conceptual exercises that clarify the fundamentals.

Out of all the stuff I have read about Promises, I have found this to be the most beneficial - what do you both think about this?

Thanks, @siraj-samsudeen – I agree that explanation you’ve linked is pretty good. I haven’t looked at any of the ones that bugged me for some weeks, so can’t directly contrast, but it seems clear and not misleading.