記一次尷尬的評論翻車事件

事情是這樣,上週四給團隊專欄發了一篇文章,發完以後忘了把帳號切回本身的帳號。而後下午搬磚中場休息的時候刷掘金,在首頁看到一篇關於限制併發請求的文章,以爲有意思就點開看了。我掃了一眼文章中的題目,沒有看文章內容,而後就開始本身解題了。尷尬的是我審錯題了,一開始用團隊帳號在評論裏寫了個 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

從代碼簡潔性來看,固然是幻☆精靈前輩的解法更優,但我以爲個人解法也不無可取之處。我提供的答案最大的好處是,它抽象出了一個通用函數,這個通用函數能複用。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息