JavaScript 面试题一则:有限并行

这是一道我常用的面试题,大部分面试者回答地都不太好,所以今天写一写。

当我们有一堆异步任务需要执行时,常见的写法为

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