大家好,我是若川。最近組織了源碼共讀活動,感興趣的可以點此加我微信 ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。
本文倉庫 https://github.com/lxchuan12/delay-analysis.git,求個star^_^[1]
源碼共讀活動 每周一期,已進行到17期。于是搜尋各種值得我們學習,且代碼行數不多的源碼。delay 主文件僅70多行[2],非常值得我們學習。
閱讀本文,你將學到:
1.?學會如何實現一個比較完善的?delay?函數
2.?學會使用?AbortController?實現取消功能
3.?學會面試常考?axios?取消功能實現
4.?等等
2. 環境準備
#?推薦克隆我的項目,保證與文章同步
git?clone?https://github.com/lxchuan12/delay-analysis.git
#?npm?i?-g?yarn
cd?delay-analysis/delay?&&?yarn?i
#?VSCode?直接打開當前項目
#?code?.
#?我寫的例子都在?examples?這個文件夾中,可以啟動服務本地查看調試
#?在?delay-analysis?目錄下
npx?http-server?examples
#?打開?http://localhost:8080#?或者克隆官方項目
git?clone?https://github.com/sindresorhus/delay.git
#?npm?i?-g?yarn
cd?delay?&&?yarn?i
#?VSCode?直接打開當前項目
#?code?.
3. delay
我們從零開始來實現一個比較完善的 delay 函數[3]。
3.1 第一版 簡版延遲
要完成這樣一個延遲函數。
3.1.1 使用
(async()?=>?{await?delay1(1000);console.log('輸出這句');
})();
3.1.2 實現
用 Promise
和 setTimeout
結合實現,我們都很容易實現以下代碼。
const?delay1?=?(ms)?=>?{return?new?Promise((resolve,?reject)?=>?{setTimeout(()?=>?{resolve();},?ms);});
}
我們要傳遞結果。
3.2 第二版 傳遞 value 參數作為結果
3.2.1 使用
(async()?=>?{const?result?=?await?delay2(1000,?{?value:?'我是若川'?});console.log('輸出結果',?result);
})();
我們也很容易實現如下代碼。傳遞 value
最后作為結果返回。
3.2.2 實現
因此我們實現也很容易實現如下第二版。
const?delay2?=?(ms,?{?value?}?=?{})?=>?{return?new?Promise((resolve,?reject)?=>?{setTimeout(()?=>?{resolve(value);},?ms);});
}
這樣寫,Promise
永遠是成功。我們也需要失敗。這時我們定義個參數 willResolve
來定義。
3.3 第三版 willResolve 參數決定成功還是失敗。
3.3.1 使用
(async()?=>?{try{const?result?=?await?delay3(1000,?{?value:?'我是若川',?willResolve:?false?});console.log('永遠不會輸出這句');}catch(err){console.log('輸出結果',?err);}
})();
3.3.2 實現
加個 willResolve
參數決定成功還是失敗。于是我們有了如下實現。
const?delay3?=?(ms,?{value,?willResolve}?=?{})?=>?{return?new?Promise((resolve,?reject)?=>?{setTimeout(()?=>?{if(willResolve){resolve(value);}else{reject(value);}},?ms);});
}
3.4 第四版 一定時間范圍內隨機獲得結果
延時器的毫秒數是寫死的。我們希望能夠在一定時間范圍內隨機獲取到結果。
3.4.1 使用
(async()?=>?{try{const?result?=?await?delay4.reject(1000,?{?value:?'我是若川',?willResolve:?false?});console.log('永遠不會輸出這句');}catch(err){console.log('輸出結果',?err);}const?result2?=?await?delay4.range(10,?20000,?{?value:?'我是若川,range'?});console.log('輸出結果',?result2);
})();
3.4.2 實現
我們把成功 delay
和失敗 reject
封裝成一個函數,隨機 range
單獨封裝成一個函數。
const?randomInteger?=?(minimum,?maximum)?=>?Math.floor((Math.random()?*?(maximum?-?minimum?+?1))?+?minimum);const?createDelay?=?({willResolve})?=>?(ms,?{value}?=?{})?=>?{return?new?Promise((relove,?reject)?=>?{setTimeout(()?=>?{if(willResolve){relove(value);}else{reject(value);}},?ms);});
}const?createWithTimers?=?()?=>?{const?delay?=?createDelay({willResolve:?true});delay.reject?=?createDelay({willResolve:?false});delay.range?=?(minimum,?maximum,?options)?=>?delay(randomInteger(minimum,?maximum),?options);return?delay;
}
const?delay4?=?createWithTimers();
實現到這里,相對比較完善了。但我們可能有需要提前結束。
3.5 第五版 提前清除
3.5.1 使用
(async?()?=>?{const?delayedPromise?=?delay5(1000,?{value:?'我是若川'});setTimeout(()?=>?{delayedPromise.clear();},?300);//?300?milliseconds?laterconsole.log(await?delayedPromise);//=>?'我是若川'
})();
3.5.2 實現
聲明 settle
變量,封裝 settle
函數,在調用 delayPromise.clear
時清除定時器。于是我們可以得到如下第五版的代碼。
const?randomInteger?=?(minimum,?maximum)?=>?Math.floor((Math.random()?*?(maximum?-?minimum?+?1))?+?minimum);const?createDelay?=?({willResolve})?=>?(ms,?{value}?=?{})?=>?{let?timeoutId;let?settle;const?delayPromise?=?new?Promise((resolve,?reject)?=>?{settle?=?()?=>?{if(willResolve){resolve(value);}else{reject(value);}}timeoutId?=?setTimeout(settle,?ms);});delayPromise.clear?=?()?=>?{clearTimeout(timeoutId);timeoutId?=?null;settle();};return?delayPromise;
}const?createWithTimers?=?()?=>?{const?delay?=?createDelay({willResolve:?true});delay.reject?=?createDelay({willResolve:?false});delay.range?=?(minimum,?maximum,?options)?=>?delay(randomInteger(minimum,?maximum),?options);return?delay;
}
const?delay5?=?createWithTimers();
3.6 第六版 取消功能
我們查閱資料可以知道有 AbortController 可以實現取消功能。
caniuse AbortController[4]
npm abort-controller[5]
mdn AbortController[6]
fetch-abort[7]
fetch#aborting-requests[8]
yet-another-abortcontroller-polyfill[9]
3.6.1 使用
(async?()?=>?{const?abortController?=?new?AbortController();setTimeout(()?=>?{abortController.abort();},?500);try?{await?delay6(1000,?{signal:?abortController.signal});}?catch?(error)?{//?500?milliseconds?laterconsole.log(error.name)//=>?'AbortError'}
})();
3.6.2 實現
const?randomInteger?=?(minimum,?maximum)?=>?Math.floor((Math.random()?*?(maximum?-?minimum?+?1))?+?minimum);const?createAbortError?=?()?=>?{const?error?=?new?Error('Delay?aborted');error.name?=?'AbortError';return?error;
};const?createDelay?=?({willResolve})?=>?(ms,?{value,?signal}?=?{})?=>?{if?(signal?&&?signal.aborted)?{return?Promise.reject(createAbortError());}let?timeoutId;let?settle;let?rejectFn;const?signalListener?=?()?=>?{clearTimeout(timeoutId);rejectFn(createAbortError());}const?cleanup?=?()?=>?{if?(signal)?{signal.removeEventListener('abort',?signalListener);}};const?delayPromise?=?new?Promise((resolve,?reject)?=>?{settle?=?()?=>?{cleanup();if?(willResolve)?{resolve(value);}?else?{reject(value);}};rejectFn?=?reject;timeoutId?=?setTimeout(settle,?ms);});if?(signal)?{signal.addEventListener('abort',?signalListener,?{once:?true});}delayPromise.clear?=?()?=>?{clearTimeout(timeoutId);timeoutId?=?null;settle();};return?delayPromise;
}const?createWithTimers?=?()?=>?{const?delay?=?createDelay({willResolve:?true});delay.reject?=?createDelay({willResolve:?false});delay.range?=?(minimum,?maximum,?options)?=>?delay(randomInteger(minimum,?maximum),?options);return?delay;
}
const?delay6?=?createWithTimers();
3.7 第七版 自定義 clearTimeout 和 setTimeout 函數
3.7.1 使用
const?customDelay?=?delay7.createWithTimers({clearTimeout,?setTimeout});(async()?=>?{const?result?=?await?customDelay(100,?{value:?'我是若川'});//?Executed?after?100?millisecondsconsole.log(result);//=>?'我是若川'
})();
3.7.2 實現
傳遞 clearTimeout, setTimeout 兩個參數替代上一版本的clearTimeout,setTimeout
。于是有了第七版。這也就是delay的最終實現。
const?randomInteger?=?(minimum,?maximum)?=>?Math.floor((Math.random()?*?(maximum?-?minimum?+?1))?+?minimum);const?createAbortError?=?()?=>?{const?error?=?new?Error('Delay?aborted');error.name?=?'AbortError';return?error;
};const?createDelay?=?({clearTimeout:?defaultClear,?setTimeout:?set,?willResolve})?=>?(ms,?{value,?signal}?=?{})?=>?{if?(signal?&&?signal.aborted)?{return?Promise.reject(createAbortError());}let?timeoutId;let?settle;let?rejectFn;const?clear?=?defaultClear?||?clearTimeout;const?signalListener?=?()?=>?{clear(timeoutId);rejectFn(createAbortError());}const?cleanup?=?()?=>?{if?(signal)?{signal.removeEventListener('abort',?signalListener);}};const?delayPromise?=?new?Promise((resolve,?reject)?=>?{settle?=?()?=>?{cleanup();if?(willResolve)?{resolve(value);}?else?{reject(value);}};rejectFn?=?reject;timeoutId?=?(set?||?setTimeout)(settle,?ms);});if?(signal)?{signal.addEventListener('abort',?signalListener,?{once:?true});}delayPromise.clear?=?()?=>?{clear(timeoutId);timeoutId?=?null;settle();};return?delayPromise;
}const?createWithTimers?=?clearAndSet?=>?{const?delay?=?createDelay({...clearAndSet,?willResolve:?true});delay.reject?=?createDelay({...clearAndSet,?willResolve:?false});delay.range?=?(minimum,?maximum,?options)?=>?delay(randomInteger(minimum,?maximum),?options);return?delay;
}
const?delay7?=?createWithTimers();
delay7.createWithTimers?=?createWithTimers;
4. axios 取消請求
axios
取消原理是:通過傳遞 config
配置 cancelToken
的形式,來取消的。判斷有傳cancelToken
,在 promise
鏈式調用的 dispatchRequest
拋出錯誤,在 adapter
中 request.abort()
取消請求,使 promise
走向 rejected
,被用戶捕獲取消信息。
更多查看我的 axios
源碼文章取消模塊 學習 axios 源碼整體架構,取消模塊(可點擊)
5. 總結
我們從零開始實現了一個帶取消功能比較完善的延遲函數。也就是 delay 70多行源碼[11]的實現。
包含支持隨機時間結束、提前清除、取消、自定義 clearTimeout、setTimeout
等功能。
取消使用了 mdn AbortController[12] ,由于兼容性不太好,社區也有了相應的 npm abort-controller[13] 實現 polyfill
。
yet-another-abortcontroller-polyfill[14]
建議克隆項目啟動服務調試例子,印象會更加深刻。
#?推薦克隆我的項目,保證與文章同步
git?clone?https://github.com/lxchuan12/delay-analysis.git
cd?delay-analysis
#?我寫的例子都在?examples?這個文件夾中,可以啟動服務本地查看調試
npx?http-server?examples
#?打開?http://localhost:8080
最后可以持續關注我@若川。歡迎加我微信 ruochuan12 交流,參與 源碼共讀 活動,每周大家一起學習200行左右的源碼,共同進步。
參考資料
[1]
本文倉庫 https://github.com/lxchuan12/delay-analysis.git,求個star^_^: https://github.com/lxchuan12/delay-analysis.git
[2]delay 主文件僅70多行: https://github.com/sindresorhus/delay/blob/main/index.js
更多點擊閱讀原文查看。
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助3000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。
識別上方二維碼加我微信、拉你進源碼共讀群
今日話題
略。分享、收藏、點贊、在看我的文章就是對我最大的支持~