2

I am leaving behind my synchronous, multi-threaded Java programming world and embracing the single-threaded, asynchronous, promise-based world of ES6 JavaScript. I have had to catch on to this concept of mapping functions written in a synchronous style to async promise-based functions. I am currently using a mix of ES6 native Promises and bluebird promises.

I'll start with an example to set the stage for my question (synchronous examples in Java, async examples in ES6):

Synchronous Function

private Object doSomething(Object var1, Object var2) {
    Object var3 = blockingFunction(var1);
    Object var4 = anotherBlockingFunction(var2, var3);
    return var4;
}

Asynchronous Promise-based equivlanet

function doSomething(var1, var2) {
    return asyncFunction(var1)
    .then(var3 => {
        return anotherAsyncFunction(var2, var3);
    })
    .then(var4 => {
        return var4;
    });
}

Now here's my question: Is there a clean (i.e. built-in, already thought of) way to simulate the synchronous return idea to bypass the rest of the return chain?

To explain, here are two more synchronous and asynchronous examples:

Synchronous Function

private Object doSomething(Object var1, Object var2) {
    Object var3 = blockingFunction(var1);
    Object var4 = anotherBlockingFunction(var2, var3);
    if (!var4.isValid()) {
        return var3;
    }
    // If var4.isValid() equates to false, we bypass the following statements
    // and return the value of var3 as the final value
    Object var5 = var4.validateObject();
    var5.setSomething(var1);
    return var5;
}

(My guess at an) Asynchronous Promise-based equivalent

function doSomething(var1, var2) {
    // Predefine variables within this block so they persist between 'then's
    let var3;
    let var4;
    let var5;
    let bypassCondition;
    return asyncFunction(var1)
    .then(result => {
        var3 = result;
        return anotherAsyncFunction(var2, var3);
    })
    .then(result => {
        var4 = result;
        bypassCondition = !var4.valid;
        if(bypassCondition) {
            return var3;
        }
    })
    .endChain(bypassCondition)
    // If the bypassCondition is true, the entire chain would return the value
    // of the previous then's return;
    // Otherwise, if false, the chain continues
    .then(() => {
        return var4.validateObject();
    })
    .then(result => {
        var5 = result;
        var5.something = var1;
        return var5;
    });
}

Does something like this already exist?

I am aware of these alternatives, so perhaps I can told whether or not either is actually the right way to do it:

  • Throw an exception in the middle of the chain and catch it at the end, so that other statements are bypassed
  • Wrap each subsequent then's function in an if (!bypassCondition) block so that they won't all be executed
Michael Plautz
  • 3,578
  • 4
  • 27
  • 40
  • 2
    Put all the subsequent `then`s in the "`else` block". There is no early exiting from promise chains, that only works with functions. You could use an `async function`, however. – Bergi Apr 05 '16 at 18:36
  • @Bergi thanks for that link. I had come across your answer to the question you linked before, but I didn't understand that it was a solution to my question until you linked it to this specific question. Though I'd agree this is a duplicate, for closure's sake, I'd like to post an answer so that it is clear how to accomplish what I am trying to do. Is that possible? – Michael Plautz Apr 05 '16 at 21:00
  • Glad to have helped by putting the link :-) There are in fact [many more pretty similar questions](https://stackoverflow.com/search?q=break+chain+[promise]). I think we normally don't allow answers to duplicate questions, but I've reopened so that you can [self-answer](https://stackoverflow.com/help/self-answer) the question. Please just put some sentence like "This answer over here [link] has helped me" at the top and leave me an upvote :-) – Bergi Apr 05 '16 at 21:05

2 Answers2

2

You're talking about conditional flow in promise-chains, of which early return is a useful flavor.

Yes this is possible, by branching your chain conditionally. Keep an eye on the parenthesis:

function doSomething(var1, var2) {
  return asyncFunction(var1)
  .then(var3 => anotherAsyncFunction(var2, var3))
  .then(var4 => (!var4.isValid())? var3 : var4.validateObject()
    .then(var5 => (var5.something = var1, var5)));
}

Note the indentation on the last line, where the .then is off of var4.validateObject().

This maps quite well to ES7 (where we can do an early bail to boot):

async function doSomething(var1, var2) {
  let var3 = await asyncFunction(var1);
  let var4 = await anotherAsyncFunction(var2, var3);
  if (!var4.isValid()) {
    return var3;
  }
  let var5 = await var4.validateObject();
  var5.something = var1;
  return var5;
}

(You didn't specify whether validateObject was async or not, so I went with async).

jib
  • 40,579
  • 17
  • 100
  • 158
  • After Bergi's suggestion to check out async functions, I now see that they ultimately accomplish what I am trying to do. Promises may be _so much_ better than plain old callbacks, but async functions appear to be _so much_ better than promises because I don't have to completely leave the sequential paradigm. They can't receive support soon enough! – Michael Plautz Apr 06 '16 at 01:43
  • @MichaelPlautz note that async functions are mere syntactic sugar on top of promises, so they go hand-in-hand (e.g. `asyncFunction` above really returns a promise). For instance, `Promise.all` etc. can still be used in async functions to run things in parallel, even in the sequential realm. – jib Apr 06 '16 at 03:41
1

This answer over here is where I was able to obtain my answer to this question.

As Bergi pointed out, what I am doing here is branching. All I need to do is put the continuing branch inside the else block, like so:

function doSomething(var1, var2) {
    // Predefine variables within this block so they persist between 'then's
    let var3;
    let var4;
    let var5;
    return asyncFunction(var1)
    .then(result => {
        var3 = result;
        return anotherAsyncFunction(var2, var3);
    })
    .then(result => {
        var4 = result;
        if(!var4.valid) {
            return var3;
        }
        else {
            return Promise.resolve()
            .then(() => {
                return var4.validateObject();
             })
             .then(result => {
                var5 = result;
                var5.something = var1;
                return var5;
             });
        })
    }
}

Fundamentally, you cannot stop a promise chain. Even though it looks like I should be able to, remember that other functions will append .then(...) to the output of your function. The code has no distinction as to if the then statements appear within one function or outside of it, so what I was proposing, .endchain(...), would have to end all other users of the promise chain outside of the function, making the chain ultimately un-useable.

Using this branching method, the traditional return behavior I was after is accomplished, because the last then in a chain before the end of the function is where the chain will pick up when its return value is used outside of the function — in this case, either after return var3; if var4.valid is false, or after return var5; otherwise.

Community
  • 1
  • 1
Michael Plautz
  • 3,578
  • 4
  • 27
  • 40