感觉二楼这里提到的异步问题,可能不用太纠结说 forEach 还是 for of 等细节写法有啥特点(用 for 来 await 估计也 ok),实际上都是正常顺序执行的。
这里异步 forEach 这种的问题主要是 JavaScript 的运行时的执行机制决定,JS 引擎主要通过单线程事件循环的方式来持续运转。我们无论用 for 还是 forEach 还是 map 啥的,当执行到一个涉及到异步操作的语句的时候,JS 引擎相当于往某个异步线程派发了一个新的任务,注册了一个回调函数,然后又继续执行下一条语句,不会等待异步操作的完成。
异步任务完成触发回调之后,引擎会把对应的回调函数的执行任务加入任务队列,然后事件循环在消费到这个队列的时候才会执行这里的回调函数;forEach 调用相当于只是按顺序启动了任务,但无法控制任务何时完成,乃至于一系列任务的回调被加入执行队列的先后顺序。
然后社区为了更好地方便开发者去控制任务的顺序,就提出了 Promise 对象的方式去做辅助,Promise 相当于代理了异步任务的回调,然后统一用 (Promise Object).then(callback)
的方式去组织这里的任务。但 Promise 一路 then 下去还是不很优雅,再进一步就发展出了 async await 这样的函数去辅助包装 Promise 的异步逻辑(类似于在语言层面把 await 后面的代码都组织到 then 的回调里面了),产生的好处是它写起来和一般的命令式写法相似,还可以很灵活地控制它们的顺序。
把握了这一点以后甚至还可以结合 map 写一些并发的异步请求逻辑(最近我自己也经常这么干~
类似这样的伪代码:
(async () => {
const imgUrlList = ['https://baidu.com/1.jpg', 'https://baidu.com/2.jpg', 'https://baidu.com/3.jpg'];
const fetchPromiseList = imgUrlList.map(async (url) => await fetch(url).blob());
const imgBlobList = await Promise.all(fetchPromiseList);
imgBlobList.forEach(imgBlob => download(imgBlob));
// ...
})();