Suppose I have the following code.
function divide(numerator, denominator) {
return new Promise((resolve, reject) => {
if(denominator === 0){
reject("Cannot divide by 0");
return; //superfluous?
}
resolve(numerator / denominator);
});
}
If my aim is to use reject
to exit early, should I get into the habit of return
ing immediately afterward as well?
The answer by Ori already explains that it is not necessary to
return
but it is good practice. Note that the promise constructor is throw safe so it will ignore thrown exceptions passed later in the path, essentially you have side effects you can't easily observe.Note that
return
ing early is also very common in callbacks:So, while it is good practice in promises it is required with callbacks. Some notes about your code:
The
return
purpose is to terminate the execution of the function after the rejection, and prevent the execution of the code after it.In this case it prevents the
resolve(numerator / denominator);
from executing, which is not strictly needed. However, it's still preferable to terminate the execution to prevent a possible trap in the future. In addition, it's a good practice to prevent running code needlessly.Background
A promise can be in one of 3 states:
When a promise is fulfilled or rejected, it will stay in this state indefinitely (settled). So, rejecting a fulfilled promise or fulfilling a rejected promise, will have not effect.
This example snippet shows that although the promise was fulfilled after being rejected, it stayed rejected.
So why do we need to return?
Although we can't change a settled promise state, rejecting or resolving won't stop the execution of the rest of the function. The function may contain code that will create confusing results. For example:
Even if the function doesn't contain such code right now, this creates a possible future trap. A future refactor might ignore the fact that the code is still executed after the promise is rejected, and will be hard to debug.
Stopping the execution after resolve/reject:
This is standard JS control flow stuff.
resolve
/reject
:resolve
/reject
- since the return value of the callback is ignored, we can save a line by returning the reject/resolve statement:I prefer to use one of the
return
options as the code is flatter.Technically it is not needed here1 - because a Promise can be resolved or rejected, exclusively and only once. The first Promise outcome wins and every subsequent result is ignored. This is different from Node-style callbacks.
That being said it is good clean practice to ensure that exactly one is called, when practical, and indeed in this case since there is no further async/deferred processing. The decision to "return early" is no different than ending any function when its work is complete - vs. continuing unrelated or unnecessary processing.
Returning at the appropriate time (or otherwise using conditionals to avoid executing the "other" case) reduces the chance of accidentally running code in an invalid state or performing unwanted side-effects; and as such it makes code less prone to 'breaking unexpectedly'.
1 This technically answer also hinges on the fact that in this case the code after the "return", should it be omitted, will not result in a side-effect. JavaScript will happily divide by zero and return either +Infinity/-Infinity or NaN.
In many cases it is possible to validate parameters separately and immediately return a rejected promise with Promise.reject(reason).
If you don't "return" after a resolve/reject, bad things (like a page redirect) can happen after you meant for it to stop. Source: I ran into this.
A common idiom, which may or may not be your cup of tea, is to combine the
return
with thereject
, to simultaneously reject the promise and exit from the function, so that the remainder of the function including theresolve
is not executed. If you like this style, it can make your code a bit more compact.This works fine because the Promise constructor does nothing with any return value, and in any case
resolve
andreject
return nothing.The same idiom can be used with the callback style shown in another answer:
Again, this works fine because the person calling
divide
does not expect it to return anything and does not do anything with the return value.