27

In my code, based on a specific condition, I would like to skip to the done function, irrespective of all the then functions.

The original version of this question is in the edits. The following is the actual problem I am dealing with. Sorry for the inconvenience

Actual Problem :

I am reading a file and processing it. If the contents of the file match certain conditions, I have to do a series of operations on the file system (say read and write few files) and then to execute done function. If the conditions fail, I have to skip all the series of operations and I have to execute the done function directly.

I return an object (lets say result) in all the then functions and in the next then I update result and return it. So, when all the then are done, done will have the accumulated result. Finally, the done will process result and print it.

So, if the conditions are not met initially, done will simply print result (which would be empty).

Q()
.then(readFile)
.then(function (contents) {
    var processResult = process the contents;
    if (processResult) {
        return {};
    } else {
        // How can I skip to `done` from here
    }
})
.then(function (results) {
    // do some more processing and update results
    return results;
})
...   // Few more then functions similar to the one above
...
.fail(function (exception) {
    console.error(exception.stack);
})
.done(function (results) {
   // do some more processing and update results
   console.log(results);
});
thefourtheye
  • 233,700
  • 52
  • 457
  • 497
  • Not sure though, but can't you return an object that holds a boolean, depending on this boolean your next function will execute yes/no. `return { randomNumber : ..., process : true };`, in your next then call: `.then(function (data) { if(!data.process) return; return data.randomNumber + 1; })` – Dieterg Feb 05 '14 at 12:46
  • @DieterGoetelen That would make the next processing function extremely reliant on the previous step though. – poke Feb 05 '14 at 12:52
  • Could you please use a more real problem (maybe your actual task, it would be fine with lots of pseudocode of what happens)? Your synchronous example doesn't really make much sense. – Bergi Feb 05 '14 at 13:19

4 Answers4

24

It depends a bit on what the conditions to skip are, what kind of operations you are doing, and how “useful” the whole thing is when the conditions failed. You might be able to use a smart rejection here to bring the message across. Otherwise, I believe the correct way to deal with this is really a nested set of promise calls.

This also matches the core idea behind promises, which is to bring back synchronous control structures to asynchronous code execution. In general, when using promises, you should first think about how you would do the task with synchronous code. And if you think about your situation, it would probably work like this:

var contents = readFromFile();
var results = initialOperation(contents);
if (fancyCondition(results)) {
     results = doSomething(results);
     results = doMore(results);
}
processAndPrint(results);

So you would have a real branch in there in synchronous code. As such, it makes no sense that you would want to avoid that in asynchronous code using promises. If you could just skip things, you were essentially using jumps with gotos. But instead, you branch off and do some other things separately.

So going back to promises and asynchronous code, having an actual branch with another set of chained operations is completely fine, and actual in spirit of the intent behind promises. So above code could look like this:

readFromFile(fileName)
.then(initialOperation)
.then(function (results) {
    if (fancyCondition(results) {
        return doSomething(results)
            .then(doMore);
    }
    return results;
})
.catch(errorHandler)
.then(processResults)
.then(outputResults); // Or `done` in Q

Also note, that the promise pipeline automatically looks a lot more cleaner when you start using functions that return promises on their own, instead of creating them inline from then.

poke
  • 369,085
  • 72
  • 557
  • 602
9

But then we are nesting the thenable functions. This is what we wanted to avoid in the first place using promises.

No, this is indeed the proper way. For branching, you will always need an additional level of nesting. If indentation is getting too large, you still can apply the old trick of calling sub-procedures (which is also used for unnesting callback functions).


Other solutions are quite ugly. Skipping a few then-handlers in a chain, without deeper nesting, can be done by throwing an exception and rejecting the promise; this could be caught in the end then. It might apply to a few scenarios, but I wouldn't consider this a good idea.

The other way I could think of would be wrapping the conditional result in another data structure, which could be passed through the chain of thens. This would be like Maybe in Haskell or Option in Scala, and you would map over them in the handlers. However, this would also require an additional level of nesting, would be less efficient to explicitly propagate nothing through the chain, and would have problems with returned promises in the chain.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

If I understand "skip" correctly, then the generic solution is not to return a value under "skip" conditions, thus allowing the input value to pass through transparently.

eg:

...then(function (randomInteger) {
    var remainder = randomInteger % 2;
    console.log(['Even','Odd'][remainder] + ' number: ', randomInteger);
    if(remainder) {
        return randomInteger + 1;
    }
})...
Beetroot-Beetroot
  • 18,022
  • 3
  • 37
  • 44
  • But this will still execute the next `then` function, right? Only difference would be, arguments is `{"0": undefined}`. – thefourtheye Feb 05 '14 at 13:03
  • Yes, you would need to apply some "skip" logic in each `.then()` success handler. That's the nature of a `.then()` chain. – Beetroot-Beetroot Feb 05 '14 at 13:05
  • And No not `undefined`, as I said, the input value is passed on if no return is made. – Beetroot-Beetroot Feb 05 '14 at 13:07
  • Of course, you could always use the fail route with null error handlers in the subsequent `.then()`s, but the eventual handler (for odd numbers in your example) would need to be a fail handler (ie `.fail(fn)` or `.then(null, fn)`). – Beetroot-Beetroot Feb 05 '14 at 13:16
0

I use a context variable and implement the conditional logic in functions called within the promise chain. Each function returns the same context variable. This keeps the promise chain tidy. Using function names that hint at the conditional tests improves readability.

function processFirstPass(context) {
    var processResult = process the contents;
    if (processResult) {
        context.result = processResult;
    }
    context.specialTag = ! processResult;
    return context;
}

processForOnlySpecialTag(context) {
    if (context.specialTag) {
        context.result = // do some more processing and update results
    }
    return context;
}

function process() {
    var context = { filename: 'input.txt' };
    readFromFile(context)
    .then(procesFirstPass)
    .then(processForOnlySpecialTag)
    .fail(function (exception) {
        console.error(exception.stack);
    })
    .done(function (context) {
       // do some more processing and update results
       console.log(context.result);
    });
}
Tongfa
  • 2,078
  • 1
  • 16
  • 14