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 里面那样。