I love 异步!but..
随手可得的异步函数可以说是 JS 里面最迷人的地方:在合适的时刻获得了合适的东西,然后做合适的事情!但是代码的流程控制不可能只有异步这一种需求,如果我们想...不断的做合适的事情,控制不当的时候甚至会导致异步和同步混杂的灾难(通往彻夜调试的直通车票get√)。
异步,同时还是异步
假设我们有一个异步发送请求,并将结果送入回调函数的API:
getSth('foo', (result)=>{
console.log(result);
});
暗含的复杂性在于,从调用到 log 结果的时间是不确定的。假设我们有一个列表 A,要对列表的每一项做一次请求,并得到相同顺序的结果列表 B,一个呼之欲出的错误写法类似这样:
for(let i = 0; i < A.length; i++){
getSth(A[i], result=>{
B.push(result);
});
}
由于每一项收到结果的时机都不确定(取决于网络),最后 push 进入 B 的顺序也会乱套的。考虑一下选择重传协议中收到乱序帧的情况,我们当然可以最后再将 B 排序一遍。
但 ES6 引入了一个更优雅的办法——Promise:
function getSthPromiseExecutor(foo){
return new Promise(function(resolve, reject){
getSth(foo, result=>{
resolve(result);
});
}));
}
let Executors = [];
for(let i = 0; i < A.length; i++){
Executors.push(getSthPromiseExecutor(A[i]));
}
Promise.all(Executors).then(resultArray=>{
console.log(resultArray);
});
这样返回到 resultArray 的还是同样顺序的结果。此时在 Promise.all
中每一个请求之间是并发的,但是所有请求都收到结果后才执行 then,获得结果数组。
异步,之后还是异步
更特殊的一种情况是,连续多个异步请求之间是相互依赖的,我们需要串行执行一系列异步请求。很不幸,Promise 并没有给这种情况带来很好的解决方案。但 ES7 草案的 async/await 给 Promise 裹上糖浆,这回终于够用了:
/*同样的异步请求函数*/
function getSthPromiseExecutor(foo){
return new Promise(function(resolve, reject){
getSth(foo, result=>{
resolve(result);
});
}));
}
(async function(){
for(let i = 0; i < A.length; i++){
let result = await getSthPromiseExecutor(A[i]);//等待结果
B.push(result);
}
})();
在 async 函数中的 await 会阻塞住流程,直到后面的 Promise 返回了结果。这次是毫无余地的串行,就像在 C 里面那样。