最近也很久沒輸出文章了,緣由很簡單,最近巨忙,,,,面試
講真的,最近也很迷茫。關於技術、關於生活吧。也找了不少在大廠的朋友去聊,想需求一些後期發展的思路。這其中也聊到了面試,聊到了招聘中會給面試者出的一些題目。我正好也很久沒面試了,就從中選了幾道。最近也會陸續出一系列關於一些面試問題的解析。segmentfault
今天這道是字節跳動的:數組
實現一個批量請求函數 multiRequest(urls, maxNum),要求以下: • 要求最大併發數 maxNum • 每當有一個請求返回,就留下一個空位,能夠增長新的請求 • 全部請求完成後,結果按照 urls 裏面的順序依次打出
這道題目我想不少同窗應該都或多或少的見過,下面我會依次從出現的場景、問題的分析到最終的實現,一步步力求深刻淺出的給出這道題目的完整解析。promise
假設如今有這麼一種場景:現有 30 個異步請求須要發送,但因爲某些緣由,咱們必須將同一時刻併發請求數量控制在 5 個之內,同時還要儘量快速的拿到響應結果。併發
應該怎麼作?異步
首先咱們來了解一下 Ajax
的串行和並行。函數
咱們平時都是基於promise
來封裝異步請求的,這裏也主要是針對異步請求來展開。fetch
經過定義一些promise實例
來具體演示串行/並行。url
var p = function () { return new Promise(function (resolve, reject) { setTimeout(() => { console.log("1000"); resolve(); }, 1000); }); }; var p1 = function () { return new Promise(function (resolve, reject) { setTimeout(() => { console.log("2000"); resolve(); }, 2000); }); }; var p2 = function () { return new Promise(function (resolve, reject) { setTimeout(() => { console.log("3000"); resolve(); }, 3000); }); }; p() .then(() => { return p1(); }) .then(() => { return p2(); }) .then(() => { console.log("end"); });
如示例,串行會從上到下依次執行對應接口請求。spa
一般,咱們在須要保證代碼在多個異步處理以後執行,會用到:
Promise.all((promises: [])).then((fun: function));
Promise.all
能夠保證,promises
數組中全部promise
對象都達到resolve
狀態,才執行then
回調。
var promises = function () { return [1000, 2000, 3000].map((current) => { return new Promise(function (resolve, reject) { setTimeout(() => { console.log(current); }, current); }); }); }; Promise.all(promises()).then(() => { console.log("end"); });
這時候考慮一個場景:若是你的promises
數組中每一個對象都是http請求
,而這樣的對象有幾十萬個。
那麼會出現的狀況是,你在瞬間發出幾十萬個http請求
,這樣頗有可能致使堆積了無數調用棧致使內存溢出。
這時候,咱們就須要考慮對Promise.all
作併發限制。
Promise.all併發限制
指的是,每一個時刻併發執行的promise
數量是固定的,最終的執行結果仍是保持與原來的Promise.all
一致。
總體採用遞歸調用來實現:最初發送的請求數量上限爲容許的最大值,而且這些請求中的每個都應該在完成時繼續遞歸發送,經過傳入的索引來肯定了urls
裏面具體是那個URL
,保證最後輸出的順序不會亂,而是依次輸出。
function multiRequest(urls = [], maxNum) { // 請求總數量 const len = urls.length; // 根據請求數量建立一個數組來保存請求的結果 const result = new Array(len).fill(false); // 當前完成的數量 let count = 0; return new Promise((resolve, reject) => { // 請求maxNum個 while (count < maxNum) { next(); } function next() { let current = count++; // 處理邊界條件 if (current >= len) { // 請求所有完成就將promise置爲成功狀態, 而後將result做爲promise值返回 !result.includes(false) && resolve(result); return; } const url = urls[current]; console.log(`開始 ${current}`, new Date().toLocaleString()); fetch(url) .then((res) => { // 保存請求結果 result[current] = res; console.log(`完成 ${current}`, new Date().toLocaleString()); // 請求沒有所有完成, 就遞歸 if (current < len) { next(); } }) .catch((err) => { console.log(`結束 ${current}`, new Date().toLocaleString()); result[current] = err; // 請求沒有所有完成, 就遞歸 if (current < len) { next(); } }); } }); }