欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JS使用Promise控制請(qǐng)求并發(fā)數(shù)

 更新時(shí)間:2023年05月25日 11:28:29   作者:JetTsang  
現(xiàn)在面試過程當(dāng)中 ,手寫題必然是少不了的,其中碰到比較多的無非就是當(dāng)屬 請(qǐng)求并發(fā)控制了,所以本文為大家整理了JS使用Promise控制請(qǐng)求并發(fā)數(shù)的示例代碼,希望對(duì)大家有所幫助

前言

現(xiàn)在面試過程當(dāng)中 ,手寫題必然是少不了的,其中碰到比較多的無非就是當(dāng)屬 請(qǐng)求并發(fā)控制了。而基本上前端項(xiàng)目都是通過axios來實(shí)現(xiàn)異步請(qǐng)求的封裝,因此這其實(shí)是考你對(duì)Promise以及異步編程的理解了。

引出

題目:

// 設(shè)計(jì)一個(gè)函數(shù),可以限制請(qǐng)求的并發(fā),同時(shí)請(qǐng)求結(jié)束之后,調(diào)用callback函數(shù)
// sendRequest(requestList:,limits,callback):void
sendRequest(
[()=>request('1'),
()=>request('2'),
()=>request('3'),
()=>request('4')],
3, //并發(fā)數(shù)
(res)=>{
    console.log(res)
})
// 其中request 可以是: 
function request (url,time=1){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log('請(qǐng)求結(jié)束:'+url);
            if(Math.random() > 0.5){
                resolve('成功')
            }else{
                reject('錯(cuò)誤;')
            }
        },time*1e3)
    })
}

明確概念

這里有幾個(gè)概念需要明確一下

  • 并發(fā):并發(fā)是多個(gè)任務(wù)同時(shí)交替的執(zhí)行(因?yàn)閏pu執(zhí)行指令的速度非常之快,它可以不必按順序一段代碼一段代碼的執(zhí)行,這樣效率反而更加低下),這樣看起來就是一起執(zhí)行的,所以叫并發(fā)。
  • 并行:可以理解為多個(gè)物理cpu或者有分布式系統(tǒng),是真正的'同時(shí)'執(zhí)行
  • 并發(fā)控制:意思是多個(gè)并發(fā)的任務(wù),一旦有任務(wù)完成,就立刻開啟下一個(gè)任務(wù)
  • 切片控制:將并發(fā)任務(wù)切片的分配出來,比如10個(gè)任務(wù),切成2個(gè)片,每片有5個(gè)任務(wù),當(dāng)前一片的任務(wù)執(zhí)行完畢,再開始下一個(gè)片的任務(wù),這樣明顯效率沒并發(fā)控制那么高了

思路

首先執(zhí)行能執(zhí)行的并發(fā)任務(wù),根據(jù)并發(fā)的概念,每個(gè)任務(wù)執(zhí)行完畢后,撈起下一個(gè)要執(zhí)行的任務(wù)。

將關(guān)鍵步驟拆分出合適的函數(shù)來組織代碼

  • 循環(huán)去啟動(dòng)能執(zhí)行的任務(wù)
  • 取出任務(wù)并且推到執(zhí)行器執(zhí)行
  • 執(zhí)行器內(nèi)更新當(dāng)前的并發(fā)數(shù),并且觸發(fā)撈起任務(wù)
  • 撈起任務(wù)里面可以觸發(fā)最終的回調(diào)函數(shù)和調(diào)起執(zhí)行器繼續(xù)執(zhí)行任務(wù)

實(shí)現(xiàn)

1.定義常量和函數(shù)

function sendRequest(requestList,limits,callback){
    // 定義執(zhí)行隊(duì)列,表示所有待執(zhí)行的任務(wù)
    const promises = requestList.slice()
    // 定義開始時(shí)能執(zhí)行的并發(fā)數(shù)
    const concurrentNum = Math.min(limits,requestList.length)
    let concurrentCount = 0 // 當(dāng)前并發(fā)數(shù)
    // 啟動(dòng)初次能執(zhí)行的任務(wù)
    const runTaskNeeded = ()=>{
        let i = 0
        while(i<concurrentNum){
            runTask()
        }
    }
    // 取出任務(wù)并推送到執(zhí)行器
    const runTask = ()=>{}
    // 執(zhí)行器,這里去執(zhí)行任務(wù)
    const runner = ()=>{}
    // 撈起下一個(gè)任務(wù)
    const picker = ()=>{}
    // 開始執(zhí)行!
    runTaskNeeded()
}

2.實(shí)現(xiàn)對(duì)應(yīng)的函數(shù)

function sendRequest(requestList,limits,callback){
    const promises = requestList.slice() // 取得請(qǐng)求list(淺拷貝一份)
    // 得到開始時(shí),能執(zhí)行的并發(fā)數(shù)
    const concurrentNum = Math.min(limits,requestList.length)
    let concurrentCount = 0 // 當(dāng)前并發(fā)數(shù)
    // 第一次先跑起可以并發(fā)的任務(wù)
    const runTaskNeeded = ()=>{
        let i = 0
        // 啟動(dòng)當(dāng)前能執(zhí)行的任務(wù)
        while(i<concurrentNum){
            i++
            runTask()
        }
    }
    // 取出任務(wù)并且執(zhí)行任務(wù)
    const runTask = ()=>{
        const task = promises.shift()
        task && runner(task)
    }
    // 執(zhí)行器
    // 執(zhí)行任務(wù),同時(shí)更新當(dāng)前并發(fā)數(shù)
    const runner = async (task)=>{
        try {
            concurrentCount++
            await task()
        } catch (error) {
        }finally{
            // 并發(fā)數(shù)--
            concurrentCount--
            // 撈起下一個(gè)任務(wù)
            picker()
        }
    }
// 撈起下一個(gè)任務(wù)
    const picker = ()=>{
        // 任務(wù)隊(duì)列里還有任務(wù)并且此時(shí)還有剩余并發(fā)數(shù)的時(shí)候 執(zhí)行
        if(concurrentCount < limits && promises.length > 0 ){
            // 繼續(xù)執(zhí)行任務(wù)
            runTask()
        // 隊(duì)列為空的時(shí)候,并且請(qǐng)求池清空了,就可以執(zhí)行最后的回調(diào)函數(shù)了
        }else if(promises.length ==0 && concurrentCount ==0 ){
            // 執(zhí)行結(jié)束
            callback && callback()
        }
    }
    // 入口執(zhí)行
    runTaskNeeded()
}

另一種實(shí)現(xiàn)

核心代碼是判斷是當(dāng)你 【有任務(wù)執(zhí)行完成】 ,再去判斷是否有剩余還有任務(wù)可執(zhí)行。 可以先維護(hù)一個(gè)pool(代表當(dāng)前執(zhí)行的任務(wù)),利用await Promise.race這個(gè)pool,不就知道是否有任務(wù)執(zhí)行完畢了嗎?

async function sendRequest(requestList,limits,callback){
    // 維護(hù)一個(gè)promise隊(duì)列
    const promises = []
    // 當(dāng)前的并發(fā)池,用Set結(jié)構(gòu)方便刪除
    const pool = new Set() // set也是Iterable<any>[]類型,因此可以放入到race里
    // 開始并發(fā)執(zhí)行所有的任務(wù)
    for(let request of requestList){
        // 開始執(zhí)行前,先await 判斷 當(dāng)前的并發(fā)任務(wù)是否超過限制
        if(pool.size >= limits){
            // 這里因?yàn)闆]有try catch ,所以要捕獲一下錯(cuò)誤,不然影響下面微任務(wù)的執(zhí)行
            await Promise.race(pool)
            .catch(err=>err)
        }
        const promise = request()// 拿到promise
        // 刪除請(qǐng)求結(jié)束后,從pool里面移除
        const cb = ()=>{
            pool.delete(promise)
        }
        // 注冊(cè)下then的任務(wù)
        promise.then(cb,cb)
        pool.add(promise)
        promises.push(promise)
    }
    // 等最后一個(gè)for await 結(jié)束,這里是屬于最后一個(gè) await 后面的 微任務(wù)
    // 注意這里其實(shí)是在微任務(wù)當(dāng)中了,當(dāng)前的promises里面是能確保所有的promise都在其中(前提是await那里命中了if)
    Promise.allSettled(promises).then(callback,callback)
}

總結(jié)一下要點(diǎn):

  • 利用race的特性可以找到 并發(fā)任務(wù) 里最快結(jié)束的請(qǐng)求
  • 利用for await 可以保證for結(jié)構(gòu)體下面的代碼是最后await 后的微任務(wù),而在最后一個(gè)微任務(wù)下,可以保證所有的promise已經(jīng)存入promises里(如果沒命中任何一個(gè)await,即限制并發(fā)數(shù)>任務(wù)數(shù)的時(shí)候,雖然不是在微任務(wù)當(dāng)中,也可以保證所有的promise都在里面),最后利用allSettled,等待所有的promise狀態(tài)轉(zhuǎn)變后,調(diào)用回調(diào)函數(shù)
  • 并發(fā)任務(wù)池 用Set結(jié)構(gòu)存儲(chǔ),可以通過指針來刪除對(duì)應(yīng)的任務(wù),通過閉包引用該指針從而達(dá)到 動(dòng)態(tài)控制并發(fā)池?cái)?shù)目
  • for await 結(jié)構(gòu)體里,其實(shí)await下面,包括結(jié)構(gòu)體外 都是屬于微任務(wù)(前提是有一個(gè)await里面的if被命中),至于這個(gè)微任務(wù)什么時(shí)候被加入微任務(wù)隊(duì)列,要看請(qǐng)求的那里的在什么時(shí)候開始標(biāo)記(resolve/reject )
  • for await 里其實(shí) 已經(jīng)在此輪宏任務(wù)當(dāng)中并發(fā)執(zhí)行了,await后面的代碼被掛起來,等前一個(gè)promise轉(zhuǎn)變狀態(tài)-->移出pool-->將下一個(gè)promise撈起加入pool當(dāng)中 -->下一個(gè)await等待最快的promise,如此往復(fù)。

可以想象這樣一個(gè)場(chǎng)景,幾組人 在玩百米接力賽,每一組分別在0m,100m,200m的地方,有幾個(gè)賽道每組就有幾個(gè)人。(注意,這里想象成 每個(gè)節(jié)點(diǎn)(比如0m處) 這幾個(gè)人是一組),每到下一個(gè)節(jié)點(diǎn)的人,將棒子交給排隊(duì)在最前面的下一個(gè)人,下一個(gè)人就開始跑。

疑問

Promise.allSettled 和race 傳入的Promise<any>[]可以被其中的觸發(fā)微任務(wù)操作增減,這樣做會(huì)改變結(jié)果嗎?

有什么能拓展的功能呢

1.想要在執(zhí)行之后得到返回所需要的結(jié)果

(在第二種方法當(dāng)中已經(jīng)實(shí)現(xiàn),第一種方法下可以 通過 增加一個(gè) task->結(jié)果 的map來收集,或者對(duì)所有的task分別包裹一層Promise,形成一個(gè)新的promiseList,放到Promise.allSettled里面,再把resolve以task->resolve的方式映射出來,在runner里面找到把Promise實(shí)例通過對(duì)應(yīng)的resolve暴露出去)

2.增加一個(gè)參數(shù)用來控制請(qǐng)求失敗的重試次數(shù)

拓展實(shí)現(xiàn)

增加重試次數(shù)以及回調(diào)函數(shù)增加返回結(jié)果

實(shí)現(xiàn)思路:

每一個(gè)請(qǐng)求 額外包裹一層promise,形成一個(gè)新的promise數(shù)組,將此數(shù)組放入Promise.allSettled,回調(diào)函數(shù)在allSettled的then里面注冊(cè)。

將用來包裹的promise 里面的 resolve和reject以及剩余重試次數(shù)等信息包裝成對(duì)象,依次放入到用來執(zhí)行的隊(duì)列當(dāng)中。此隊(duì)列的作用為,執(zhí)行時(shí)取出,往后如果要重試,則重新加入到此隊(duì)列。

實(shí)現(xiàn)1的改造

增加參數(shù)retryTimes:number來表示重試次數(shù)

注意是重試次數(shù),不是一共請(qǐng)求的次數(shù)。

大綱

function sendRequest(requestList, limits, callback, retryTimes) {
    // 定義執(zhí)行隊(duì)列,表示所有待執(zhí)行的任務(wù)
    const requestListWrapperedQueue = [];
    // 定義開始時(shí)能執(zhí)行的并發(fā)數(shù)
    const concurrentNum = Math.min(limits, requestList.length);
    // 定義放在allSettled的所有promise
    const returnPromises = []
    // 當(dāng)前并發(fā)數(shù)
    let concurrentCount = 0;
    // 新增: 包裹promise,并且將相關(guān)信息重新包裝放入請(qǐng)求隊(duì)列
    const wrapePromise = (requestItem)=>{}
    // 啟動(dòng)初次能執(zhí)行的任務(wù)
    const runTaskNeeded = () => {}; // 取出任務(wù)并推送到執(zhí)行器
    const runTask = () => {};
    // 執(zhí)行器,這里去執(zhí)行任務(wù)
    const runner = (task) => {};
    // 撈起下一個(gè)任務(wù)
    const picker = () => {};
    // 新增: 初始化,構(gòu)建執(zhí)行隊(duì)列以及包裹promise
    const init = ()=>{}
    // 開始執(zhí)行函數(shù)
    const start = ()=>{}
    // 開始
    start()
    // 新增:
    Promise.allSettled(returnPromises).then(callback,callback)
}

完整實(shí)現(xiàn)

function sendRequest(requestList, limits, callback, retryTimes) {
    // 定義執(zhí)行隊(duì)列,表示所有待執(zhí)行的任務(wù)
    const requestListWrapperedQueue = [];
    // 定義開始時(shí)能執(zhí)行的并發(fā)數(shù)
    const concurrentNum = Math.min(limits, requestList.length);
    // 定義放在allSettled的所有promise
    const returnPromises = [];
    // 當(dāng)前并發(fā)數(shù)
    let concurrentCount = 0;
    // 新增: 包裹promise,并且將相關(guān)信息重新包裝放入請(qǐng)求隊(duì)列
    const wrapePromise = (requestItem)=>{
        return new Promise((resolve,reject)=>{
            // 構(gòu)建執(zhí)行隊(duì)列
            requestListWrapperedQueue.push({
                requestFn:requestItem,  // 請(qǐng)求函數(shù)放到此處
                resolve,
                reject,
                remainRetryTime:retryTimes // 剩余重試次數(shù)
            })
        })
    };
    // 啟動(dòng)初次能執(zhí)行的任務(wù)
    const runTaskNeeded = () => {
        let i = 0
        // 啟動(dòng)當(dāng)前的任務(wù)
        while(i < concurrentNum){
            i++
            runTask()
        }
    };
    // 取出任務(wù)并推送到執(zhí)行器
    const runTask = () => {
        const task = requestListWrapperedQueue.shift()
        task && runner(task)
    };
    // 執(zhí)行器,這里去執(zhí)行任務(wù)
    const runner = async (task) => {
        const {
            requestFn,
            resolve,
            reject,
            remainRetryTime
        } = task;
        try {
            // 并發(fā)數(shù) +1
            concurrentCount++
            // 執(zhí)行任務(wù)
            const res = await requestFn()
            // 拿到結(jié)果,直接結(jié)束
            resolve(res)
        } catch (error) {
            // 判斷還有無重試次數(shù)
            if(remainRetryTime > 0){
                // 重新放回隊(duì)列,注意這樣并不會(huì)影響allSettled結(jié)果的順序
                requestListWrapperedQueue.push(task)
                // 剩余重試次數(shù)-1
                task.remainRetryTime --
            }else {
                // 沒有剩余次數(shù)則直接結(jié)束
                reject(error)
            }
        }finally{
            // 并發(fā)數(shù)-1
            concurrentCount--
            // 撈起下一個(gè)任務(wù)
            picker()
        }
    };
    // 撈起下一個(gè)任務(wù)
    const picker = () => {
        if(concurrentCount < limits && requestListWrapperedQueue.length > 0 ){
            // 繼續(xù)執(zhí)行任務(wù)
            runTask()
        }
    };
    // 新增: 初始化,構(gòu)建執(zhí)行隊(duì)列以及包裹promise
    const init = ()=>{
        for(let requestItem of requestList){
            const wrapperedPromise = wrapePromise(requestItem)
            // 構(gòu)建包裹promise的數(shù)組,用于allSettled
            returnPromises.push(wrapperedPromise)
        }
    }
    // 開始執(zhí)行函數(shù)
    const start = ()=>{
        init()
        runTaskNeeded()
    }
    // 開始
    start()
    // 新增:allSettled用來獲取結(jié)果
    Promise.allSettled(returnPromises).then(callback,callback)
}

結(jié)尾

這種題目是考驗(yàn)?zāi)銓?duì)異步編程的理解,要想寫出來,你需要具備事件循環(huán)以及promise的知識(shí)。

到此這篇關(guān)于JS使用Promise控制請(qǐng)求并發(fā)數(shù)的文章就介紹到這了,更多相關(guān)JS控制請(qǐng)求并發(fā)數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論