How can I bypass the rest of the 'then's i

2019-07-24 14:27发布

问题:

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

回答1:

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).



回答2:

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.