I. Implementing Sequential Task Queues
A task queue means that after A completes, it calls B; after B completes, it calls C (where A, B, C all represent asynchronous operations)... The old approach was nested callbacks, with code like:
taskA(data1, function(res) {
if (res.state === OK) {
taskB(data2, function(res) {
if (res.state === OK) {
taskC(data3, function(res) {
if (res.state === OK) {
// ...
}
else {
// err3
}
});
}
else {
// err2
}
});
}
else {
// err1
}
});
Each step can potentially go wrong (err1, err2, err3...), and error handling may differ, making the code structure very bloated (uncontrollably "expanding horizontally", eventually becoming "a mess" of code). Since each step is asynchronous + callback, it's difficult to separate the various callbacks.
The most obvious effect of Promise is eliminating this "callback pyramid"; code no longer expands horizontally:
// taskX all return Promise objects
taskA(data1).then(
taskB(data2),
errHandler1
).then(
taskC(data3),
errHandler2
).then(
displayData,
errHandler3
);
If taskA and taskB have the same error handling, it can be even simpler:
// taskX all return Promise objects
taskA(data1).then(taskB(data2)).then(
taskC(data3),
errHandler12
).then(
displayData,
errHandler3
);
If you want to handle errors uniformly, you can do this:
// taskX all return Promise objects
taskA(data1).then(taskB(data2)).then(taskC(data3)).then(displayData).catch(errHandler);
// Equivalent to
taskA(data1).then(taskB(data2)).then(taskC(data3)).then(displayData, errHandler);
Chained calls + automatic error propagation forward, writes nicely and reads well too.
II. Implementing Sequential Task Pipelines
A task pipeline means the output of the current task can serve as input for the next task, forming a data pipeline. jQuery code looks like:
$.post(url1, data, function(res) {
if (res.state === OK) {
$.post(url2, res.data, function(res) {
if (res.state === OK) {
$.post(url3, res.data, function(res) {
if (res.state === OK) {
// ...
}
else {
// err3
}
});
}
else {
// err2
}
});
}
else {
// err1
}
});
Actual scenarios are very common, such as getting parameter userId from url1, then using it to exchange for third-party openId from url2, finally exchanging for orderList from url3, then showing the result to the user. Similar logic is naturally a task pipeline.
Using Promise, it can be implemented like this:
new Promise(function(resolve, reject) {
resolve(1);
}).then(function(res) {
return new Promise(function(resolve, reject) {
resolve(res + 1);
});
}).then(function(res) {
return new Promise(function(resolve, reject) {
resolve(res + 1);
});
}).then(function(res) {
console.log(res);
});
// => 3
P.S. jQuery's $.when() is a kind of Promise implementation, but differs somewhat from the Promise A+ specification. For details, see jQuery deferred and promise object methods
III. Handling Sequential Tasks with Runtime-Determined Execution Order
Suppose there are 3 tasks A, B, C, with uncertain execution order—possibly BCA, BAC, etc., determined only at runtime. Once the order is determined, they must be executed sequentially immediately.
At this point, callback nesting can no longer satisfy the requirement, since each order would need corresponding callback nesting. We need a mechanism to freely combine callbacks, such as Promise:
var tasks = getOrderedTasks();
var p = tasks[0];
tasks.forEach(function(item, index) {
p.then(item);
});
The side effect of writing it this way is that the pipeline is lost (task output cannot be passed down as input for the next task). But that's okay; it's easy to fix:
function catchFuture(future) {
// Process/pass future
// ...
console.log('catchFuture: ' + future);
}
var tasks = getOrderedTasks();
var p = tasks[0];
tasks.forEach(function(item, index) {
// p.then(item);
p.then(item.then(catchFuture));
});
No comments yet. Be the first to share your thoughts.