I'm using the Q module for Node.js in attempts to avoid the "pyramid of doom" in scenarios where I have many steps. For example:
function doTask(task, callback)
{
Q.ncall(task.step1, task)
.then(function(result1){
return Q.ncall(task.step2, task);
})
.then(function(result2){
return Q.ncall(task.step3, task);
})
.fail(callback).end();
}
Essentially this seems to work; if an error is thrown by any of the task steps, it is passed to the callback (though I would be welcome to improvements, as I am new to node.js promises). However, I have a problem when I need to abort the task-chain early. For example, if result1 is successfully returned I might want to call the callback early and abort the rest, but my attempts to do so are failing...
function doTask(task, callback)
{
Q.ncall(task.step1, task)
.then(function(result1){
if(result1)
{// the rest of the task chain is unnecessary
console.log('aborting!');
callback(null, result1);
return null;
}
return Q.ncall(task.step2, task);
})
.then(function(result2){
console.log('doing step 3...');
return Q.ncall(task.step3, task);
})
.fail(callback).end();
}
In this example, I see both "aborting!" and "doing step 3..." printed.
I'm sure I'm merely misunderstanding some basic principles here, so would appreciate any help. Thanks!
This is a case where you will need to branch, which does mean either nesting or creating a subroutine.
function doTask(task, callback) {
return Q.ncall(task.step1, task)
.then(function(result1) {
if (result1) return result1;
return Q.ncall(task.step2, task)
.then(function(result2) {
return Q.ncall(task.step3, task);
})
})
.nodeify(callback)
}
Or
function doTask(task, callback) {
return Q.ncall(task.step1, task)
.then(function(result1) {
if (result1) {
return result1;
} else {
return continueTasks(task);
}
})
.nodeify(callback)
}
function continueTasks(task) {
return Q.ncall(task.step2, task)
.then(function(result2) {
return Q.ncall(task.step3, task);
})
}
Any errors that are thrown within the promise chain will cause the entire stack to be aborted early and control is given to the error-back path. (in this case, the fail() handler) When you detect a certain state which causes you to want to abort the promise chain, then just throw a very specific error, which you trap in the error-back and ignore (if you so choose)
function doTask(task, callback)
{
Q.ncall(task.step1, task)
.then(function(result1){
if(result1 == 'some failure state I want to cause abortion')
{// the rest of the task chain is unnecessary
console.log('aborting!');
throw new Error('abort promise chain');
return null;
}
return Q.ncall(task.step2, task);
})
.then(function(result2){
console.log('doing step 3...');
return Q.ncall(task.step3, task);
})
.fail(function(err) {
if (err.message === 'abort promise chain') {
// just swallow error because chain was intentionally aborted
}
else {
// else let the error bubble up because it's coming from somewhere else
throw err;
}
})
.end();
}