一.实现串行任务队列
任务队列是指A完成了调用B,B完成了调用C(当然,这里的ABC都表示异步操作)…以前的做法是嵌套回调,代码类似于:
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
}
});
其间每一步都可能出错(err1, err2, err3…),错误处理也可能不同,代码结构会变得非常臃肿(不可控制地“横向发展”,最终变成“一坨”代码)。因为每一步都是异步 + 回调,所以很难把各个callback分离开
Promise最明显的效果就是消除了这种“回调金字塔”,代码不会再横向发展:
// taskX都返回Promise对象
taskA(data1).then(
taskB(data2),
errHandler1
).then(
taskC(data3),
errHandler2
).then(
displayData,
errHandler3
);
如果taskA和taskB的错误处理相同,还可以更简单:
// taskX都返回Promise对象
taskA(data1).then(taskB(data2)).then(
taskC(data3),
errHandler12
).then(
displayData,
errHandler3
);
如果想统一处理错误,可以这样做:
// taskX都返回Promise对象
taskA(data1).then(taskB(data2)).then(taskC(data3)).then(displayData).catch(errHandler);
// 等价于
taskA(data1).then(taskB(data2)).then(taskC(data3)).then(displayData, errHandler);
链式调用 + 错误自动后抛,写出来好看,读起来也不错
二.实现串行任务管道
任务管道是指当前任务的输出可以作为下一个任务的输入,形成一条数据管道,jQuery代码类似于:
$.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
}
});
实际场景很常见,比如从url1获取参数userId,拿到后再从url2换取第三方openId,最后再从url3换取orderList,然后把结果展示给用户,类似的逻辑都是天然的任务管道
用Promise可以这样实现:
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的$.when()就是一种Promise实现,但与Promise A+规范有些差异,详细请查看jQuery deffered和promise对象方法
三.处理运行时才能确定执行顺序的串行任务
假设有A,B,C3个任务,执行顺序不确定,有可能是BCA、BAC等等,运行时才能确定3者的执行顺序,一旦确定顺序,就要立即串行执行
此时回调嵌套已经无法满足了,因为每一种顺序都要对应一种回调嵌套,我们需要一种能够随意组合回调的机制,比如Promise:
var tasks = getOrderedTasks();
var p = tasks[0];
tasks.forEach(function(item, index) {
p.then(item);
});
这样写的副作用是管道没了(任务的输出无法传递下去作为下一个任务的输入),不过没关系,很容易修改:
function catchFuture(future) {
// 处理/传递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));
});