成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

前端開發中大并發量如何控制并發數

開發 前端
在這篇文章中,簡要介紹了為什么要進行并發請求,闡述了使用請求池隊列實現并發請求的設計思路,簡要實現代碼。此外,還閱讀分析了p-limit的源碼,并使用數組進行簡要的源碼編寫,以實現要求。

寫在前面

最近在進行移動端h5開發,首頁需要加載的資源很多,一個lottie動效需要請求70多張圖片,但是遇到安卓webview限制請求并發數,導致部分圖片請求失敗破圖。當然圖片資源可以做閑時加載和預加載,可以減輕播放動效時資源未加載的問題。

同樣的,業務開發也會遇到需要異步請求幾十個接口,如果同時并發請求瀏覽器會進行限制請求數,也會給后端造成請求壓力。

場景說明

現在有個場景:

請你實現一個并發請求函數concurrencyRequest(urls, maxNum),要求如下:

  • 要求最大并發數 maxNum。
  • 每當有一個請求返回,就留下一個空位,可以增加新的請求。
  • 所有請求完成后,結果按照 urls 里面的順序依次打出(發送請求的函數可以直接使用fetch即可)。

初始實現:

const preloadManger = (urls, maxCount = 5) => {
  let count = 0; // 計數 -- 用于控制并發數
  const createTask = () => {
    if (count < maxCount) {
      const url = urls.pop(); // 從請求數組中取值
      if (url) {
        // 無論請求是否成功,都要執行taskFinish
        loader(url).finally(taskFinish);
        // 添加下一個請求
        count++;
        createTask();
      }
    }
  };

  const taskFinish = () => {
    count--;
    createTask();
  };

  createTask();
};

// 進行異步請求
const loader = async (url) => {
  const res = await fetch(url).then(res=>res.json());
  console.log("res",res);
  return res
}

const urls = [];
for (let i = 1; i <= 20; i++) {
    urls.push(`https://jsonplaceholder.typicode.com/todos/${i}`);
}

preloadManger(urls, 5)

請求狀態:

可以看到上面的請求是每五個一組進行請求,當一個請求無論返回成功或是失敗,都會從請求數組中再取一個請求進行補充。

設計思路

那么,我們可以考慮使用隊列去請求大量接口。

思路如下:

假定最大并發數是maxNum=5,圖中對接口進行了定義編號,當請求隊列池中有一個請求返回后,就向池子中新增一個接口進行請求,依次直到最后一個請求執行完畢。

當然,要保證程序的健壯性,需要考慮一些邊界情況,如下:

  • 當初始請求數組urls的長度為0時,此時請求結果數組results是個空數組。
  • 最大并發數maxNums>urls的長度時,請求數為urls的長度。
  • 需要定義計數器count去判斷是否全部請求完畢。
  • 無論請求成功與否,都應該將結果存在結果數組results中。
  • 結果數組results和urls數組的順序保持一致,方便存取。

代碼實現

在前面的初始實現的代碼中,雖然都能滿足基本需求,但是并沒有考慮一些邊界條件,對此需要根據上面設計思路重新實現得到:

// 并發請求函數
const concurrencyRequest = (urls, maxNum) => {
    return new Promise((resolve) => {
        if (urls.length === 0) {
            resolve([]);
            return;
        }
        const results = [];
        let index = 0; // 下一個請求的下標
        let count = 0; // 當前請求完成的數量

        // 發送請求
        async function request() {
            if (index === urls.length) return;
            const i = index; // 保存序號,使result和urls相對應
            const url = urls[index];
            index++;
            console.log(url);
            try {
                const resp = await fetch(url);
                // resp 加入到results
                results[i] = resp;
            } catch (err) {
                // err 加入到results
                results[i] = err;
            } finally {
                count++;
                // 判斷是否所有的請求都已完成
                if (count === urls.length) {
                    console.log('完成了');
                    resolve(results);
                }
                request();
            }
        }

        // maxNum和urls.length取最小進行調用
        const times = Math.min(maxNum, urls.length);
        for(let i = 0; i < times; i++) {
            request();
        }
    })
}

測試代碼:

const urls = [];
for (let i = 1; i <= 20; i++) {
    urls.push(`https://jsonplaceholder.typicode.com/todos/${i}`);
}
concurrencyRequest(urls, 5).then(res => {
    console.log(res);
})

請求結果:

上面代碼基本實現了前端并發請求的需求,也基本滿足需求,在生產中其實有很多已經封裝好的庫可以直接使用。比如:p-limit【https://github.com/sindresorhus/p-limit】

閱讀p-limit源碼

import Queue from 'yocto-queue';
import {AsyncResource} from '#async_hooks';

export default function pLimit(concurrency) {
 // 判斷這個參數是否是一個大于0的整數,如果不是就拋出一個錯誤
 if (
  !((Number.isInteger(concurrency)
  || concurrency === Number.POSITIVE_INFINITY)
  && concurrency > 0)
 ) {
  throw new TypeError('Expected `concurrency` to be a number from 1 and up');
 }

 // 創建隊列 -- 用于存取請求
 const queue = new Queue();
 // 計數
 let activeCount = 0;

 // 用來處理并發數的函數
 const next = () => {
  activeCount--;

  if (queue.size > 0) {
   // queue.dequeue()可以理解為[].shift(),取出隊列中的第一個任務,由于確定里面是一個函數,所以直接執行就可以了;
   queue.dequeue()();
  }
 };

 // run函數就是用來執行異步并發任務
 const run = async (function_, resolve, arguments_) => {
  // activeCount加1,表示當前并發數加1
  activeCount++;

  // 執行傳入的異步函數,將結果賦值給result,注意:現在的result是一個處在pending狀態的Promise
  const result = (async () => function_(...arguments_))();

  // resolve函數就是enqueue函數中返回的Promise的resolve函數
  resolve(result);

  // 等待result的狀態發生改變,這里使用了try...catch,因為result可能會出現異常,所以需要捕獲異常;
  try {
   await result;
  } catch {}

  next();
 };

 // 將run函數添加到請求隊列中
 const enqueue = (function_, resolve, arguments_) => {
  queue.enqueue(
   // 將run函數綁定到AsyncResource上,不需要立即執行,對此添加了一個bind方法
   AsyncResource.bind(run.bind(undefined, function_, resolve, arguments_)),
  );

  // 立即執行一個異步函數,等待下一個微任務(注意:因為activeCount是異步更新的,所以需要等待下一個微任務執行才能獲取新的值)
  (async () => {
   // This function needs to wait until the next microtask before comparing
   // `activeCount` to `concurrency`, because `activeCount` is updated asynchronously
   // when the run function is dequeued and called. The comparison in the if-statement
   // needs to happen asynchronously as well to get an up-to-date value for `activeCount`.
   await Promise.resolve();

   // 判斷activeCount是否小于concurrency,并且隊列中有任務,如果滿足條件就會將隊列中的任務取出來執行
   if (activeCount < concurrency && queue.size > 0) {
    // 注意:queue.dequeue()()執行的是run函數
    queue.dequeue()();
   }
  })();
 };

 // 接收一個函數fn和參數args,然后返回一個Promise,執行出隊操作
 const generator = (function_, ...arguments_) => new Promise(resolve => {
  enqueue(function_, resolve, arguments_);
 });

 // 向外暴露當前的并發數和隊列中的任務數,并且手動清空隊列
 Object.defineProperties(generator, {
  // 當前并發數
  activeCount: {
   get: () => activeCount,
  },
  // 隊列中的任務數
  pendingCount: {
   get: () => queue.size,
  },
  // 清空隊列
  clearQueue: {
   value() {
    queue.clear();
   },
  },
 });

 return generator;
}

整個庫只有短短71行代碼,在代碼中導入了yocto-queue庫,它是一個微型的隊列數據結構。

手寫源碼

在進行手撕源碼時,可以借助數組進行簡易的實現:

class PLimit {
    constructor(concurrency) {
        this.concurrency = concurrency;
        this.activeCount = 0;
        this.queue = [];
        
        return (fn, ...args) => {
            return new Promise(resolve => {
               this.enqueue(fn, resolve, args);
            });
        }
    }
    
    enqueue(fn, resolve, args) {
        this.queue.push(this.run.bind(this, fn, resolve, args));

        (async () => {
            await Promise.resolve();
            if (this.activeCount < this.concurrency && this.queue.length > 0) {
                this.queue.shift()();
            }
        })();
    }
    
    async run(fn, resolve, args) {
        this.activeCount++;

        const result = (async () => fn(...args))();

        resolve(result);

        try {
            await result;
        } catch {
        }

        this.next();
    }
    
    next() {
        this.activeCount--;

        if (this.queue.length > 0) {
            this.queue.shift()();
        }
    }
}

小結

在這篇文章中,簡要介紹了為什么要進行并發請求,闡述了使用請求池隊列實現并發請求的設計思路,簡要實現代碼。

此外,還閱讀分析了p-limit的源碼,并使用數組進行簡要的源碼編寫,以實現要求。

參考文章

  • 【源碼共讀】大并發量如何控制并發數https://juejin.cn/post/7179220832575717435?searchId=20240430092814392DC2208C545E691A26
  • 前端實現并發控制網絡請求https://mp.weixin.qq.com/s/9uq2SqkcMSSWjks0x7RQJg。
責任編輯:姜華 來源: 宇宙一碼平川
相關推薦

2017-08-21 10:56:55

MySQL并發控制

2024-06-17 08:40:16

2021-04-07 06:00:18

JavaScript 前端并發控制

2021-01-12 10:22:45

JavaScript并發控制前端

2009-09-24 14:43:53

Hibernate樂觀

2022-12-12 09:07:06

Redis并發限流

2009-06-09 09:32:48

LinuxApache帶寬控制

2023-11-20 08:01:38

并發處理數Tomcat

2010-11-08 10:57:05

SQL Server的

2021-06-29 23:40:19

Golang語言并發

2021-02-25 22:17:19

開發技術編程

2009-11-25 11:41:56

IIS最大并發數

2017-02-28 17:46:15

Linux驅動技術并發控制

2009-02-09 10:06:03

并發控制Web應用悲觀鎖

2023-01-30 15:41:10

Channel控制并發

2017-11-06 17:16:55

Linux設備驅動并發控制

2024-08-26 13:23:26

2020-01-13 10:20:30

架構聊天架構百萬并發量

2025-02-26 03:00:00

2025-02-28 00:03:22

高并發TPS系統
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: av日韩在线播放 | 日韩免费激情视频 | 国产精品.xx视频.xxtv | 国产精品美女久久久久久免费 | 91免费电影 | 国产亚洲精品美女久久久久久久久久 | 国产精品123区 | 国产三区av | 国产福利网站 | 国产ts人妖系列高潮 | 亚洲一区二区三区在线免费 | 国产精品久久精品 | 免费毛片网| 在线日韩| 亚洲视频精品 | 国产无套一区二区三区久久 | 蜜桃av一区二区三区 | 免费在线观看91 | 一区二区三区在线免费观看视频 | 丁香婷婷在线视频 | 精品福利在线视频 | 男女羞羞免费网站 | 亚洲视频在线播放 | 美女久久| 91精品久久久久久久99 | 精精国产xxxx视频在线播放 | 亚洲精品久久久 | 中文字幕不卡视频在线观看 | 成人av播放 | 成人性视频免费网站 | 久久毛片网站 | 97色在线视频 | 欧美极品在线观看 | 日韩在线观看视频一区 | 婷婷福利 | 欧美日韩三级在线观看 | 欧美日韩一区精品 | 午夜精品一区二区三区在线播放 | 自拍中文字幕 | 中文字幕第一页在线 | 一区二区三区国产好 |