记一次尴尬的评论翻车事件

事情是这样,上周四给团队专栏发了一篇文章,发完以后忘了把帐号切回本身的帐号。而后下午搬砖中场休息的时候刷掘金,在首页看到一篇关于限制并发请求的文章,以为有意思就点开看了。我扫了一眼文章中的题目,没有看文章内容,而后就开始本身解题了。尴尬的是我审错题了,一开始用团队帐号在评论里写了个 naive 的答案。然而更尴尬的是在我意识到本身审错题以后,还写不出正确答案……编程

题目是这样的:数组

req

忽略我一开始那个 naive 的答案,在我准备正确地解这个题的时候,想的是写一个通用方法,限制异步请求能并发执行的次数。那个通用函数写出来了,长这样:promise

const limitConcurrency = (fn, max) => {
  const pendingTasks = new Set();
  return async function(...args) {
    while (pendingTasks.size >= max) {
      await Promise.race(pendingTasks);
    }

    const promise = fn.apply(this, args);
    const res = promise.catch(() => {});
    pendingTasks.add(res);
    await res;
    pendingTasks.delete(res);
    return promise;
  };
};
复制代码

这个 limitConcurrency 函数在闭包里记录了当前还没 resolve 的 promise,而后在 while 循环里判断当前进行中的异步操做是否达到了设定的上限;若是达到了就用 Promise.race 等最快的那个异步执行完;当并发的异步操做数量没有达到上限时,继续执行当前的异步操做,并将当前的异步操做加到 pendingTasks 里面,在当前异步操做 resolve 的时候再将其从 pendingTask 里面删掉。闭包

而后再回到题目,一开始我思惟比较局限,想着我必需要等最后一个异步请求执行完再执行回调。怎么判断全部请求都执行完了呢?我又用了一个 naive 的方法,当 urls 数组里面最后一个请求执行完了,我就当全部请求执行完了。我最后这样写的:并发

function sendRequest(urls, max, callback) {
    const limitFetch = limitConcurrency(fetch, max);
    
    async function go(urlList){
        const [head, ...tail] = urlList;
        if(tail.length === 0) {
            await limitFetch(head);
            return callback();
        }
        limitFetch(head);
        go(tail);
    }
    go(urls);
}
复制代码

动脑子想想也知道最后一个请求不必定是最后执行完。可是我卡在这里了,没办法了,而后我发沸点求助了,而后各路英雄各显神通,看到他们的答案我感到怀疑人生,这么简单的问题我怎么就卡壳了?app

精彩答案有不少,这里我只挑出我以为最精彩的答案,来自幻☆精灵异步

function sendRequest(urls, max, callback) {
  const len = urls.length;
  let idx = 0;
  let counter = 0;

  function _request() {
    // 有请求,有通道
    while (idx < len && max > 0) {
      max--; // 占用通道
      fetch(urls[idx++]).finally(() => {
        max++; // 释放通道
        counter++;
        if (counter === len) {
          return callback();
        } else {
          _request();
        }
      });
    }
  }
  _request();
}
复制代码

这个答案如此精巧简洁,我今天上午看到这个答案,而后出去登山,整个途中都在回味这段代码的精妙…… 它用最少的信息表达了异步和并发的本质。写出这种代码,须要的不单单是编程技巧,更多的是对异步和并发的理解。在我完整理解了这段代码以后,晚上回到家再来看我以前的答案,发现我离正确答案就只差一丢丢了!async

最终完整答案以下:函数

const limitConcurrency = (fn, max) => {
  const pendingTasks = new Set();
  return async function(...args) {
    while (pendingTasks.size >= max) {
      await Promise.race(pendingTasks);
    }

    const promise = fn.apply(this, args);
    const res = promise.catch(() => {});
    pendingTasks.add(res);
    await res;
    pendingTasks.delete(res);
    return promise;
  };
};

async function sendRequest(urls, max, callback) {
  const limitFetch = limitConcurrency(fetch, max);
  await Promise.all(urls.map(limitFetch));
  callback();
}
复制代码

limitConcurrency 已经保证了个人所有请求的并发上限,我只须要用 Promise.all 来处理每一个并发频道的最后请求就好了……post

从代码简洁性来看,固然是幻☆精灵前辈的解法更优,但我以为个人解法也不无可取之处。我提供的答案最大的好处是,它抽象出了一个通用函数,这个通用函数能复用。

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息