这是一道我常用的面试题,大部分面试者回答地都不太好,所以今天写一写。
当我们有一堆异步任务需要执行时,常见的写法为
async function fetch (url) { // 请求一个URL并返回其响应,省略其实现 } // 写法 1 const results = await Promise.all(urls.map(url => fetch(url))); // 写法 2 const results = []; for (let i = 0; i < urls.length; i += 1) { results.push(await fetch(urls[i])) }
这是一个开放性问题,我一般会问被试者这两个写法会有什么问题,如何优化。
这里给一下我的答案。
写法 1 是完全并行的,当 urls 数组很大时,大量的 Promise 和函数上下文的创建可能会导致性能问题。
而写法 2 是完全串行的,问题在于并行度低,效率差。
所以可以引入一个工具函数 parallel
async function parallel(jobs, fn, workerCount = 5) { const ret = new Array(jobs.length); let cursor = 0; async function worker(workerId) { let currentJob; while (cursor < jobs.length) { try { currentJob = cursor; cursor += 1; ret[currentJob] = await fn(jobs[currentJob]); } catch (e) { console.log(`worker: ${workerId} job: ${currentJob}`, e); } } } const workers = []; for (let i = 0; i < workerCount && i < jobs.length; i += 1) { workers.push(worker(i)); } await Promise.all(workers); return ret; }
则上面的写法改为
const results = await parallel(urls, (url) => fetch(url), 5)
可以控制并行的规模,并且用法上和 map 写法类似。
parallel 的实现利用了 JavaScript 基于事件循环的单线程异步和 run to completion 语义,顺便考察了常用的异步API的使用,在白板面试的条件下完成需要比较强的代码功底。
P.S. 这个写法很容易联想到其它语言的多线程实现,比如 Python/C++/ Java,可以横向对照。
饥人谷一直致力于培养有灵魂的编程者,打造专业有爱的国内前端技术圈子。如造梦师一般帮助近千名不甘寂寞的追梦人把编程梦变为现实,他们以饥人谷为起点,足迹遍布包括facebook、阿里巴巴、百度、网易、京东、今日头条、大众美团、饿了么、ofo在内的国内外大小企业。 了解培训课程:加微信 xiedaimala03,官网:https://jirengu.com