[consolog]

Promise Flow Control Dilemma

April 4, 2017

Note: With async and await syntax, this is no longer a problem:

var input = getInput()
if (sanityCheck(input)) {
    if (isEdit) {
        var obj = await db.findById(id).exec()
        await update(obj, input)
    } else {
        obj = await newObject(input)
    }
    await modifyAndSaveObject(obj)
} else {
    throw(new Error('Invalid input'))
}

Promise in flow control IF…ELSE…

In sequential programming, we can easily use if...else to diverge the code into different control flow paths, then after that, merge the control together. However, in asynchronous programing, such flexibility is sometimes not easy to do.

I was creating a page with a capability to create and edit a database object and ran into this problem:

  1. The function I used to edit the object returns a promise;
  2. The same function also do creating a new object, because creating and editing are similar actions;
  3. In the function, I used a boolean varible isEdit to diverge the code;
    • If it is editing, I find the editing object by id;
    • If it is creating, I create a new object;
  4. Then the code converged and do a bunch of work on the object, and save it.

In a sequential code, it is very straight forward:

var input = getInput()
if (isEdit) {
    var obj = db.findById(id)
}
if (sanityCheck(input)) {
    if (isEdit) {
        update(obj, input)
    } else {
        obj = newObject(input)
    }
    modifyAndSaveObject(obj)
} else {
    throw(new Error('Invalid input'))
}

With Javascript async nature, I want to return a promise. So use promise to do the same logic, however, notice in this case, the modifyAndSaveObject() is referenced twice here:

var input = getInput()
if (sanityCheck(input)) {
    if (isEdit) {
        return db.findById(id).exec()
        .then(obj => {
            return update(obj, input)
        })
        .then(obj => {
            return modifyAndSaveObject(obj)
        })
    } else {
        return newObject(input)
        .then(obj => {
            return modifyAndSaveObject(obj)
        })
    }
} else {
    throw(new Error('Invalid input'))
}

The control flow in this case diverged from the if (isEdit) point and never merged after. Therefore, we have to call modifyAndSaveObject(obj) twice in each branch!

Because in async mode, once you have diverged the code with promise, there is no free merge anymore. The only possible merge point will be a catch which will catch any throw/rejection from different branches.

This forces me to create a function modifyAndSaveObject(obj) and put all logic there, while if in sequential code, I don’t need to do that and just put the logic of modifyAndSaveObject after the if...else... block.

Which way is better? It is hard to say. But definitely the async mode is little bit more complex and rigid.

programming