【经典面试题系列】实现网络请求并发限制

1,049 阅读3分钟

经典2.jpg

今天给大家带来一道经典的面试手写题:实现网络请求并发限制。具体题目如下:

实现网络请求并发限制的管理类 NetManager(concurrency),构造函数传入一个number,是并发的限制数,提供 request(url, options) => Promise方法。

假设可以使用 httpRequest(url, options) 这个函数来发实际的网络请求

function httpRequest(url, options){
  // 真实的网络请求省略
  return new Promise((resolve, reject) => {
    // xxx
  });
}

class NetManager {
  constructor(concurrency) {
    
  }
  request(url, data) {
    
  }
}

整理一下题目的要求,要实现下面这2点:

  1. 实现一个类 class ,内部会有网络请求的并发限制,如果达到限制,则内部维持队列处理

  2. 调用方在使用 NetManagerrequest 方法时,不考虑是否达到了并发限制。也就是说,并发限制对调用方是透明 的,简单说,就是不管是否达到了并发限制, request 方法始终返回的是一个 Promise

根据上面的2点分析,我们可以先写出大概的轮廓,代码如下:

class NetManager {
  constructor(concurrency) {
    // 保存并发数
    this.concurrency = concurrency;
    // 待处理队列
    this.queue = [];
  }
  request(url, data) {
    return new Promise((resolve, reject) => {
      httpRequest(url, data).then(resolve, reject);
    });
  }

目前 request 的函数实现,能满足对外使用(不管是否超出并发数)始终不变,接下来增加并发限制。

比较容易想到的实现方法是,我们可以把所有的请求,都放到队列里 ,再维持一个当前处理中的个数N,当处理中个数N小于并发限制时,从队列最前面拿出第一个请求,发出去。根据这个想法,进一步完善代码如下:

class NetManager {
  constructor(concurrency) {
    // 保存并发数
    this.concurrency = concurrency;
    // 待处理队列
    this.queue = [];
    // 处理中的个数
    this.processCount = 0;
  }
  request(url, data) {
    return new Promise((resolve, reject) => {
      // 这里不再直接发请求,而是放到队列里
      // 这里把这个 promise 的 resolve/reject 先和请求参数一起放到队列里,
      // 等待实际请求之后再改变promise状态,把最终数据(异常)返回给调用方
      this.queue.push({
        url,
        data,
        resolve,
        reject,
      });
      // 尝试处理第一个任务
      this._processNext();
    });
  }
  // 内部私有函数,判断当前是否超过并发数,未超过就从队列取出第1个发送请求
  _processNext() {
    if (this.processCount >= this.concurrency) {
      // 超过并发数,啥都不做
      return;
    }
    const obj = this.queue.shift();
    if (obj) {
      // 有待处理任务,取出来发送
      this.processCount++;
      const p = httpRequest(obj.url, obj.data);
      p.finally((res) => {
        // 网络请求完成,不管成功还是失败,都将进行中减1
        this.processCount--;
        // 尝试处理下一个任务
        this._processNext();
      });
      // 把实际的网络返回数据(失败错误)传给调用方
      p.then(obj.resolve, obj.reject);
    }
  }
}

我们就这样一步步实现了这个 NetManager 类。其实这个题目也不是空想出来的,毕竟在浏览器内部,本身就对单个域名的并发数有限制,有时在一些性能优化的场景下,我们也需要手动控制网络的顺序、优先级,或者并发数,希望通过这道题目,能给大家一些基本的思路。

PS: 这个题目还可以继续追问,如果要_在内部实现网络请求的缓存_,大概会怎么实现,过程中可能有哪些注意点,感兴趣的同学可以自己想一想 :)

参考资料

原文地址

前端刷题网站