異步的防抖

異步抖動

咱們能夠把每次異步操做(Promise或者setTimeout)當作一個單獨的異步線程。在實際的編程過程當中,大多數狀況下咱們對異步線程是不作監管、讓其自生自滅的,但這樣容易引起一些問題。編程

好比某個組件A,其中請求了後端定位解析服務;而當A組件被使用到一個列表中時,列表的for循環會形成n次請求併發。咱們能夠將這種狀況,當作是異步行爲產生了抖動。後端

異步防抖

下面這段僞代碼,描述了咱們平常所進行的防抖處理的基本要點:緩存

const debounce = (func, delay = 500) => {
    let timeout = 0;
    return (...args) => {
        // 若是沒有阻斷
        if(!timeout){
            // 那麼開始阻斷
            timeout = setTimeout(() => {
                // delay以後解除阻斷
                timeout = 0;
            }, delay)
            // 當即執行
            func(...args);
        } else {
            // 什麼也不作
        }
    }
}
複製代碼

能夠看出,這種防抖的主要思路,是把delay延遲期間的行爲都阻斷。咱們能夠把這當作是一種行爲防抖,在鼠標點擊、鼠標移動等事件綁定場景中常常使用。併發

可是,在異步操做中,好比上面的案例1,for循轉中組件的異步請求行爲若是被阻斷,那每一個組件都所須要的數據也就拿不到,也沒法經過resolve或reject觸發下一步邏輯。這種狀況下,咱們須要作數據防抖dom

數據防抖的流程,大體分如下幾步:異步

  1. 在第一次異步請求發起以後,掛起後續全部請求;
  2. 在第一次異步請求返回以後,向全部掛起的請求共享數據
  3. 當全部掛起隊列被清空後,Reset狀態和數據。

簡單講,就是把一組異步請求,都交給第一發請求來作,剩餘的僅等待請求結果。這有點相似進程和線程的關係。同時,由於不一樣http請求的url不同,其返回數據也是不一樣的,因此須要對每次異步請求按url進行分組,對共享的數據也須要按url進行隔離。測試

簡單的實現

首先模擬一個異步請求:ui

let somePromise = (key) => new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([key, Math.random()]);
    }, 500 + 500 * Math.random())
});
複製代碼

咱們能夠使用參數url對httpGet請求進行分組。url

下面是基於Promise所作的防抖spa

const debouncePromise = (factory, keyIndex = 0, delay = 50) => {
    // 共享數據空間
    const cache = {};
    return (...args) => new Promise((resolve, reject) => {
        // 獲取緩存分組
        let key = args[keyIndex];
        let state = cache[key];
        if(!state){
            state = { status: 0, taskCount: 0 };
            cache[key] = state;
        }
        // 首發請求,可看爲主線程
        if(state.status === 0){
            // 鎖定狀態,掛起其餘請求。
            state.status = 1;
            factory(...args).then(result => {
                // 結束自身異步行爲
                resolve(result);
                // 共享數據
                state.result = result;
                // 解鎖狀態,通知其餘請求。
                state.status = 2;
            }, err => {
                reject(err);
                state.result = err;
                // 解鎖狀態,通知其餘請求。
                state.status = 3;
            })
        }
        // 其餘請求,可看爲輔線程,僅等待主線程結果。
        else if(state.status === 1){
            // 任務數+1
            state.taskCount += 1;
            const waitingHandle = setInterval(() => {
                // 已解鎖狀態,2或3
                if(state.status > 1){
                    // 清理等待循環
                    clearInterval(waitingHandle);
                    // 處理結果
                    (state.status === 2 ? resolve : reject)(state.result);
                    // 任務數-1
                    state.taskCount -= 1;
                    // 若是任務數歸零,說明自身是最後一個線程
                    // reset狀態和數據
                    if(state.taskCount <= 0){
                        delete cache[key];
                    }
                }
            }, delay)
        }
    })
}
複製代碼

測試代碼:

somePromise('aaaaa').then(res => console.log(res))
somePromise('aaaaa').then(res => console.log(res))
somePromise('aaaaa').then(res => console.log(res))

// debounce it
somePromise = debouncePromise(somePromise)

somePromise('bbb').then(res => console.log(res))
somePromise('bbb').then(res => console.log(res))
somePromise('bbb').then(res => console.log(res))
somePromise('cc').then(res => console.log(res))
somePromise('cc').then(res => console.log(res))
somePromise('cc').then(res => console.log(res))

// 低於delay閾值再次推入隊列
// 期待結果應與上面的bbb分組一致。
setTimeout(() => {
  somePromise('bbb').then(res => console.log(res))
  somePromise('bbb').then(res => console.log(res))
  somePromise('bbb').then(res => console.log(res))
}, 10)

// 高於delay閾值從新發起請求
// 期待結果應與上面的bbb分組不一致。
setTimeout(() => {
  somePromise('bbb').then(res => console.log(res))
  somePromise('bbb').then(res => console.log(res))
  somePromise('bbb').then(res => console.log(res))
}, 1000)
複製代碼

執行結果:

// 防抖前,每次請求結果抖動。
["aaaaa", 0.6301757853487]
["aaaaa", 0.2816070377500479]
["aaaaa", 0.009064307010989259]
//防抖後,delay閾值內結果不抖動
["bbb", 0.43005402041935437]
["bbb", 0.43005402041935437]
["bbb", 0.43005402041935437]
["cc", 0.956314414078062]
["cc", 0.956314414078062]
["cc", 0.956314414078062]
// delay閾值內認爲是抖動,保持數據共享
["bbb", 0.43005402041935437]
["bbb", 0.43005402041935437]
["bbb", 0.43005402041935437]
// delay閾值外認爲是新的請求
["bbb", 0.7923457809536392]
["bbb", 0.7923457809536392]
["bbb", 0.7923457809536392]
複製代碼

執行結果符合期待。

示例代碼:codepen.io/marvin_2019…

相關文章
相關標籤/搜索