SB2055 SB2055 - 2 days ago 3
Javascript Question

How to break a promise chain?


I'm aware that there are similar questions, but I haven't seen any that address this chaining pattern.


I have the following:

var runTests = function (chain, resolutionTest) {
return chain.then(function (result) {
if (result)
return result; // Early return if the previous tests were successful. This is where I want to prevent other attempts.
const attempt = tryOpenStream(resolutionTest).then(streamToDom);
return attempt;
});
}

// from someplace else
numTests = resolutionTests.length;
return resolutionTests.reduce(runTests, Promise.resolve()); // start reduce with an empty promise


The problem that I'm encountering is that I'm calling
tryOpenStream
many times even after I've captured a
result
.

Options I'm considering:


  • Raise some global flag that just prevents further execution from within the chain. Yuck, because the chain still proceeds, it's just emptied.

  • throw new Error(result)
    instead of
    return result
    . This would break the chain (I think...) but it's misusing
    Error
    and would be easily misunderstood by another developer.



How can I break this chain at
return result;
?

UPDATE 1

I'm trying the following:

var makeTest = function (runMoreTests, resolutionTest) {
return function runTest() {
return tryOpenStream(resolutionTest).then(streamToDom).then(function (result) {
if (result)
return result;
else
return runMoreTests();
});
};
}

return resolutionTestBuilder.buildTests().then(function (resolutionTests) {
numTests = resolutionTests.length;
return resolutionTests.reduceRight(makeTest, function () { Promise.reject("No resolutions succeeded.") })();
});


However no calls to
runTest
are invoked. This is a bit of new syntax for me so I'll research some and update with any findings.

UPDATE 2

I was missing the
()
to invoke the
reduceRight
. Though now I'm seeing that
reject
called even with success... though when I step through, that rejection isn't invoked. It's as if by the time I get a result back, all links in the chain have been invoked.

Answer

Both flags and exceptions can be used, but as you noticed they're not the proper tool.

Instead, use recursion, like in @IsiahMeadows' answer, or a right fold:

var makeTest = function (runMoreTests, resolutionTest) {
    return function runTest(result) {
        if (result)
            return result;
        return tryOpenStream(resolutionTest).then(streamToDom).then(runMoreTests);
    };
}

return Promise.resolve(resolutionTests.reduceRight(makeTest, x => x)(undefined));

or better written as

var makeTest = function (runMoreTests, resolutionTest) {
    return function runTest() {
        return tryOpenStream(resolutionTest).then(streamToDom).then(result => {
            if (result)
                return result;
            else
                return runMoreTests();
        });
    };
}

return resolutionTests.reduceRight(makeTest, () => Promise.reject("nothing succeeded"))();