Promise Flow Control Dilemma
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:
- The function I used to edit the object returns a promise;
- The same function also do creating a new object, because creating and editing are similar actions;
- 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;
- If it is editing, I find the editing object by
- 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.