The return
purpose is to terminate the execution of the function after the rejection, and prevent the execution of the code 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 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:
- pending - initial state. From pending we can move to one of the other states
- fulfilled - successful operation
- rejected - failed operation
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.
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 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:
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 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.
- Return after the
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 with the
resolve
/ reject
- since the return value of the callback is ignored, we can save a line 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));
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.