Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Break javascript promise chain in a clean way

I am trying to chain promises so that the chain will break if one promise is rejected. I followed the leads of a previous SO question and tried to apply it to native promises, but I think I am misunderstanding the way things work.

Here is how I have rewritten the code:

Promise.resolve()
    .then(function() {
        return step(1)
            .then(null, function() {
                stepError(1);
            });
    })
    .then(function() {
        return step(2)
            .then(null, function() {
                stepError(2);
            });
    })
    .then(function() {
        return step(3)
            .then(null, function() {
                stepError(3);
            });
    });

function step(n) {
    console.log('Step '+n);
    return (n === 2) ? Promise.reject(n) : Promise.resolve(n);
}

function stepError(n) {
    console.log('Error '+n);
    return Promise.reject(n);
}

The output of the above code is:

Step 1
Step 2
Error 2
Step 3
[UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): 2]

In my understanding, step 2 should break the chain and step 3 should not be executed. When step(2) returns a rejected promise, stepError(2) is executed as expected. But since it returns Promise.reject(2), the function in the next then should not be executed, and since there is not catch in the end, the rejected promise of step 2 seems - as expected - to be forwarded until it exits the chain because it didn't find any handler.

What am I missing here ?

Here is a JSFiddle to play with: https://jsfiddle.net/6p4t9xyk/

like image 550
user6822275 Avatar asked Dec 11 '16 11:12

user6822275


1 Answers

In my understanding, step 2 should break the chain...

It would, but you've accidentally converted that rejection into a resolution.

The key thing about promises is that every call to then creates a new promise which is resolved/rejected based on what the then callback(s) do, and the callback processing a rejection converts that rejection into a resolution unless it intentionally does otherwise.

So here:

return step(2)
    .then(null, function() {  // This handler converts the
        stepError(2);         // rejection into a resolution
    });                       // with the value `undefined`

That's so that you can have error handlers that compensate for the error.

Since stepError returns a rejection, you could continue the rejection by just adding a return:

return step(2)
    .then(null, function() {
        return stepError(2);  // Added `return`
    });

...or alternately, remove that handler entirely:

return step(2);

...or you could throw in the callback, which is automatically turned into a rejection.

The unhandled rejection warning is caused by the fact nothing consumes the rejection from stepError.


Here's an example returning the result of stepError:

Promise.resolve()
    .then(function() {
        return step(1)
            .then(null, function() {
                return stepError(1); // Added `return`
            });
    })
    .then(function() {
        return step(2)
            .then(null, function() {
                return stepError(2); // Added `return`
            });
    })
    .then(function() {
        return step(3)
            .then(null, function() {
                return stepError(3); // Added `return`
            });
    });

function step(n) {
    console.log('Step '+n);
    return (n === 2) ? Promise.reject(n) : Promise.resolve(n);
}

function stepError(n) {
    console.log('Error '+n);
    return Promise.reject(n);
}
like image 60
T.J. Crowder Avatar answered Oct 03 '22 23:10

T.J. Crowder