Should I return after early authorization / rejection?

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 goal is to use reject to exit earlier, should I get used to return right after that?

+96
javascript promise es6-promise
12 Sep '15 at 6:45
source share
5 answers

The purpose of return is to stop the function from being rejected and to prevent code from executing after it.

 function divide(numerator, denominator) { return new Promise((resolve, reject) => { if (denominator === 0) { reject("Cannot divide by 0"); return; // The function execution ends here } resolve(numerator / denominator); }); } 

In this case, it prevents the execution of resolve(numerator / denominator); that is strictly not required. However, it is still preferable to stop execution to prevent a possible trap in the future. In addition, it is good practice to prevent code uptime.

Background

A promise can be in one of three states:

  • deferred - initial state. From the waiting, we can go into one of the other states.
  • done - successful operation
  • Failed - failed operation

When a promise is fulfilled or rejected, it will remain in this state for an indefinite time (settled). Thus, the rejection of a fulfilled promise or the fulfillment of a rejected promise will not affect.

This example fragment shows that although the promise was fulfilled after rejection, it remained rejected.

 function divide(numerator, denominator) { return new Promise((resolve, reject) => { if (denominator === 0) { reject("Cannot divide by 0"); } resolve(numerator / denominator); }); } divide(5,0) .then((result) => console.log('result: ', result)) .catch((error) => console.log('error: ', error)); 

So why should we come back?

Although we cannot change the state of the promised promise, rejection or resolution will not stop the rest of the function. A function may contain code that creates confusing results. For example:

 function divide(numerator, denominator) { return new Promise((resolve, reject) => { if (denominator === 0) { reject("Cannot divide by 0"); } console.log('operation succeeded'); resolve(numerator / denominator); }); } divide(5, 0) .then((result) => console.log('result: ', result)) .catch((error) => console.log('error: ', error)); 

Even if the function does not contain such code right now, this creates a possible future trap. A future refactor may ignore the fact that the code is still executed after the promise is rejected, and it will be difficult for us to debug.

Stop execution after authorization / rejection:

This is standard JS control flow material.

  • Return after resolve / reject :

 function divide(numerator, denominator) { return new Promise((resolve, reject) => { if (denominator === 0) { reject("Cannot divide by 0"); return; } console.log('operation succeeded'); resolve(numerator / denominator); }); } divide(5, 0) .then((result) => console.log('result: ', result)) .catch((error) => console.log('error: ', error)); 
  • Return using resolve / reject - since the return value of the callback is ignored, we can save the string by returning the reject / resolve statement:

 function divide(numerator, denominator) { return new Promise((resolve, reject) => { if (denominator === 0) { return reject("Cannot divide by 0"); } console.log('operation succeeded'); resolve(numerator / denominator); }); } divide(5, 0) .then((result) => console.log('result: ', result)) .catch((error) => console.log('error: ', error)); 
  • Using the if / else block:

 function divide(numerator, denominator) { return new Promise((resolve, reject) => { if (denominator === 0) { reject("Cannot divide by 0"); } else { console.log('operation succeeded'); resolve(numerator / denominator); } }); } divide(5, 0) .then((result) => console.log('result: ', result)) .catch((error) => console.log('error: ', error)); 

I prefer to use one of the return options, as the code is flatter.

+135
Sep 12 '15 at 6:50
source share

A common idiom, which may or may not be your cup of tea, is combining return with reject to simultaneously reject the promise and exit the function, so the rest of the function, including resolve not executed. If you like this style, it can make your code a little more compact.

 function divide(numerator, denominator) { return new Promise((resolve, reject) => { if (denominator === 0) return reject("Cannot divide by 0"); ^^^^^^^^^^^^^^ resolve(numerator / denominator); }); } 

This works fine because the Promise constructor does nothing with any return value, and either resolve and reject do not return anything.

You can use the same idiom with the callback style shown in another answer:

 function divide(nom, denom, cb){ if(denom === 0) return cb(Error("Cannot divide by zero")); ^^^^^^^^^ cb(null, nom / denom); } 

Again, this works fine, because the person calling the divide does not expect him to return something and will not do anything with the return value.

+17
Mar 19 '16 at 7:26
source share

Technically, this is not necessary here 1 - because a promise can be resolved or rejected exclusively and only once. The result of the first promise wins, and each subsequent result is ignored. This is different from Node callbacks.

Considering that this is a good pure practice, to ensure that exactly one is called when it is practical, and valid in this case, since there is no further processing of asynchronous / deferred processing. The decision to "return to an early date" is no different from the completion of any function when its work is completed - against continuing unrelated or unnecessary processing.

Returning at the appropriate time (or otherwise using conditional expressions to avoid the execution of a “different” case) reduces the likelihood of accidentally running the code in an invalid state or performing unwanted side effects; and as such, it makes the code less prone to "unexpected hacking."




1 This technical answer also depends on the fact that in this case the code after “returning”, if it is omitted, will not lead to a side effect. JavaScript will happily divide by zero and return either + Infinity / -Infinity or NaN.

+5
Sep 12 '15 at 7:05
source share

The answer from Ori already explains that there is no need to return , but this is good practice. Note that the promise constructor is a safe throw, so it ignores the thrown exceptions thrown later in the path, essentially you have side effects that you cannot easily observe.

Note that the early start of return also very common in callbacks:

 function divide(nom, denom, cb){ if(denom === 0){ cb(Error("Cannot divide by zero"); return; // unlike with promises, missing the return here is a mistake } cb(null, nom / denom); // this will divide by zero. Since it a callback. } 

So, although good practice in promises is required with callbacks. Some notes about your code:

  • Your use case is hypothetical, don't really use promises with synchronous actions.
  • The promise constructor ignores return values. Some libraries will warn you if you return a value not undefined to warn you of an error returning there. Most of them are not so smart.
  • The promise constructor is a safe throw, it converts exceptions to deviations, but, as others have pointed out, promises to allow once.
+5
Sep 12 '15 at 12:04
source share

If you do not “come back” after allowing / rejecting, bad things can happen (for example, page redirection) after you stop. Source: I came across this.

+2
Dec 12 '16 at 6:27
source share



All Articles