异步编程一直是JavaScript 编程的重大事项。关于异步方案, ES6 先是出现了 基于状态管理的 Promise,然后出现了 Generator 函数 + co 函数,紧接着又出现了 ES7 的 async + await 方案。
本文力求以最简明的方式来疏通 async + await。
异步编程的几个场景
先从一个常见问题开始:一个for 循环中,如何异步的打印迭代顺序?
我们很容易想到用闭包,或者 ES6 规定的 let 块级作用域来回答这个问题。
for (let val of [1, 2, 3, 4]) { setTimeout(() => console.log(val),100);}// => 预期结果依次为:1, 2, 3, 4
这里描述的是一个均匀发生的的异步,它们被依次按既定的顺序排在异步队列中等待执行。
如果异步不是均匀发生的,那么它们被注册在异步队列中的顺序就是乱序的。
for (let val of [1, 2, 3, 4]) { setTimeout(() => console.log(val), 100 * Math.random());}// => 实际结果是随机的,依次为:4, 2, 3, 1
返回的结果是乱序不可控的,这本来就是最为真实的异步。但另一种情况是,在循环中,如果希望前一个异步执行完毕、后一个异步再执行,该怎么办?
for (let val of ['a', 'b', 'c', 'd']) { // a 执行完后,进入下一个循环 // 执行 b,依此类推}
这不就是多个异步 “串行” 吗!
在回调 callback 嵌套异步操作、再回调的方式,不就解决了这个问题!或者,使用 Promise + then() 层层嵌套同样也能解决问题。但是,如果硬是要将这种嵌套的方式写在循环中,还恐怕还需费一番周折。试问,有更好的办法吗?
异步同步化方案
试想,如果要去将一批数据发送到服务器,只有前一批发送成功(即服务器返回成功的响应),才开始下一批数据的发送,否则终止发送。这就是一个典型的 “for 循环中存在相互依赖的异步操作” 的例子。
明显,这种 “串行” 的异步,实质上可以当成同步。它和乱序的异步比较起来,花费了更多的时间。按理说,我们希望程序异步执行,就是为了 “跳过” 阻塞,较少时间花销。但与之相反的是,如果需要一系列的异步 “串行”,我们应该怎样很好的进行编程?
对于这个 “串行” 异步,有了 ES6 就非常容易的解决了这个问题。
async function task () { for (let val of [1, 2, 3, 4]) { // await 是要等待响应的 let result = await send(val); if (!result) { break; } }}task();
从字面上看,就是本次循环,等有了结果,再进行下一次循环。因此,循环每执行一次就会被暂停(“卡住”)一次,直到循环结束。这种编码实现,很好的消除了层层嵌套的 “回调地狱” 问题,降低了认知难度。
新闻热点
疑难解答
图片精选