1

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.

SB2055
  • 12,272
  • 32
  • 97
  • 202
  • 1
    Have you also seen [these](https://stackoverflow.com/q/28803287/1048572) [few](https://stackoverflow.com/q/21576862/1048572) [questions](https://stackoverflow.com/q/29499582/1048572)? – Bergi Jun 20 '17 at 16:31
  • 1
    Re Upd 2: Not sure why you get a rejected promise. Are you sure that one of the `streamToDom` calls does return a truthy result, and `runMoreTests` is not always invoked? Can you do some debugging, or post the output of `console.log(result)` injected into that callback? – Bergi Jun 20 '17 at 17:04
  • @Bergi I am seeing `return runMoreTests()` invoked several times, then `return result`, then `return runMoreTests()` many more times until the rejection is invoked. – SB2055 Jun 20 '17 at 17:09
  • 1
    Hm, after the `return result` nothing else should be run. You do use a proper promise implementation that never calls `then` callbacks multiple times, right? I can't imagine another reason. – Bergi Jun 20 '17 at 17:14

2 Answers2

1

Try using this:

function runTests(resolutionTest) {
    return tryOpenStream(resolutionTest).then(streamToDom)
}

// from someplace else
function loop(tests, i) {
    if (i === tests.length) return undefined
    return runTests(tests[i]).then(function (result) {
        if (result) return result
        return loop(tests, i + 1)
    })
}
return loop(resolutionTests, 0)

Although I do wonder why you can't use an exception to denote your tryOpenStream failed. That would actually simplify your code some.

Claudia
  • 1,197
  • 15
  • 30
1

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"))();
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Just updated my question with my attempt at what you're suggesting - thanks for the help – SB2055 Jun 20 '17 at 16:53
  • 1
    I have to admit that the right fold is rather esoteric and hard to understand, you might want to go with the simpler (and possible more performant) recursive approach. – Bergi Jun 20 '17 at 17:03
  • Yeah I'm playing with it an although I don't fully understand it, recursion scares me even more. My original implementation was built on recursion and debugging was a nightmare. I have updated my question once again with the results I'm seeing - it is working though I'm seeing some interesting results (rejection being invoked always) – SB2055 Jun 20 '17 at 17:06
  • 1
    Try to understand the recursive solution first, my approach builds upon it but uses the fold for traversing the array. Not sure why it would not work, but I didn't test it either so maybe it has a flaw. – Bergi Jun 20 '17 at 17:09