面试官:请手写一个带取消功能的延迟函数,axios 取消功能的原理是什么
大家好,我是若川。最近組織了源碼共讀活動,感興趣的可以點此加我微信 ruochuan12?參與,每周大家一起學(xué)習(xí)200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學(xué)習(xí)源碼整體架構(gòu)系列》?包含20余篇源碼文章。
本文倉庫 https://github.com/lxchuan12/delay-analysis.git,求個star^_^[1]
源碼共讀活動 每周一期,已進行到17期。于是搜尋各種值得我們學(xué)習(xí),且代碼行數(shù)不多的源碼。delay 主文件僅70多行[2],非常值得我們學(xué)習(xí)。
閱讀本文,你將學(xué)到:
1.?學(xué)會如何實現(xiàn)一個比較完善的?delay?函數(shù) 2.?學(xué)會使用?AbortController?實現(xiàn)取消功能 3.?學(xué)會面試常考?axios?取消功能實現(xiàn) 4.?等等2. 環(huán)境準(zhǔn)備
#?推薦克隆我的項目,保證與文章同步 git?clone?https://github.com/lxchuan12/delay-analysis.git #?npm?i?-g?yarn cd?delay-analysis/delay?&&?yarn?i #?VSCode?直接打開當(dāng)前項目 #?code?. #?我寫的例子都在?examples?這個文件夾中,可以啟動服務(wù)本地查看調(diào)試 #?在?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?直接打開當(dāng)前項目 #?code?.3. delay
我們從零開始來實現(xiàn)一個比較完善的 delay 函數(shù)[3]。
3.1 第一版 簡版延遲
要完成這樣一個延遲函數(shù)。
3.1.1 使用
(async()?=>?{await?delay1(1000);console.log('輸出這句'); })();3.1.2 實現(xiàn)
用 Promise 和 setTimeout 結(jié)合實現(xiàn),我們都很容易實現(xiàn)以下代碼。
const?delay1?=?(ms)?=>?{return?new?Promise((resolve,?reject)?=>?{setTimeout(()?=>?{resolve();},?ms);}); }我們要傳遞結(jié)果。
3.2 第二版 傳遞 value 參數(shù)作為結(jié)果
3.2.1 使用
(async()?=>?{const?result?=?await?delay2(1000,?{?value:?'我是若川'?});console.log('輸出結(jié)果',?result); })();我們也很容易實現(xiàn)如下代碼。傳遞 value 最后作為結(jié)果返回。
3.2.2 實現(xiàn)
因此我們實現(xiàn)也很容易實現(xiàn)如下第二版。
const?delay2?=?(ms,?{?value?}?=?{})?=>?{return?new?Promise((resolve,?reject)?=>?{setTimeout(()?=>?{resolve(value);},?ms);}); }這樣寫,Promise 永遠是成功。我們也需要失敗。這時我們定義個參數(shù) willResolve 來定義。
3.3 第三版 willResolve 參數(shù)決定成功還是失敗。
3.3.1 使用
(async()?=>?{try{const?result?=?await?delay3(1000,?{?value:?'我是若川',?willResolve:?false?});console.log('永遠不會輸出這句');}catch(err){console.log('輸出結(jié)果',?err);} })();3.3.2 實現(xiàn)
加個 willResolve 參數(shù)決定成功還是失敗。于是我們有了如下實現(xiàn)。
const?delay3?=?(ms,?{value,?willResolve}?=?{})?=>?{return?new?Promise((resolve,?reject)?=>?{setTimeout(()?=>?{if(willResolve){resolve(value);}else{reject(value);}},?ms);}); }3.4 第四版 一定時間范圍內(nèi)隨機獲得結(jié)果
延時器的毫秒數(shù)是寫死的。我們希望能夠在一定時間范圍內(nèi)隨機獲取到結(jié)果。
3.4.1 使用
(async()?=>?{try{const?result?=?await?delay4.reject(1000,?{?value:?'我是若川',?willResolve:?false?});console.log('永遠不會輸出這句');}catch(err){console.log('輸出結(jié)果',?err);}const?result2?=?await?delay4.range(10,?20000,?{?value:?'我是若川,range'?});console.log('輸出結(jié)果',?result2); })();3.4.2 實現(xiàn)
我們把成功 delay 和失敗 reject 封裝成一個函數(shù),隨機 range 單獨封裝成一個函數(shù)。
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();實現(xiàn)到這里,相對比較完善了。但我們可能有需要提前結(jié)束。
3.5 第五版 提前清除
3.5.1 使用
(async?()?=>?{const?delayedPromise?=?delay5(1000,?{value:?'我是若川'});setTimeout(()?=>?{delayedPromise.clear();},?300);//?300?milliseconds?laterconsole.log(await?delayedPromise);//=>?'我是若川' })();3.5.2 實現(xiàn)
聲明 settle變量,封裝 settle 函數(shù),在調(diào)用 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 可以實現(xiàn)取消功能。
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 實現(xiàn)
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 函數(shù)
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 實現(xiàn)
傳遞 clearTimeout, setTimeout 兩個參數(shù)替代上一版本的clearTimeout,setTimeout。于是有了第七版。這也就是delay的最終實現(xiàn)。
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 鏈?zhǔn)秸{(diào)用的 dispatchRequest 拋出錯誤,在 adapter 中 request.abort() 取消請求,使 promise 走向 rejected,被用戶捕獲取消信息。
更多查看我的 axios 源碼文章取消模塊 學(xué)習(xí) axios 源碼整體架構(gòu),取消模塊(可點擊)
5. 總結(jié)
我們從零開始實現(xiàn)了一個帶取消功能比較完善的延遲函數(shù)。也就是 delay 70多行源碼[11]的實現(xiàn)。
包含支持隨機時間結(jié)束、提前清除、取消、自定義 clearTimeout、setTimeout等功能。
取消使用了 mdn AbortController[12] ,由于兼容性不太好,社區(qū)也有了相應(yīng)的 npm abort-controller[13] 實現(xiàn) polyfill。
yet-another-abortcontroller-polyfill[14]
建議克隆項目啟動服務(wù)調(diào)試例子,印象會更加深刻。
#?推薦克隆我的項目,保證與文章同步 git?clone?https://github.com/lxchuan12/delay-analysis.git cd?delay-analysis #?我寫的例子都在?examples?這個文件夾中,可以啟動服務(wù)本地查看調(diào)試 npx?http-server?examples #?打開?http://localhost:8080最后可以持續(xù)關(guān)注我@若川。歡迎加我微信 ruochuan12 交流,參與 源碼共讀 活動,每周大家一起學(xué)習(xí)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
更多點擊閱讀原文查看。
·················?若川簡介?·················
你好,我是若川,畢業(yè)于江西高校。現(xiàn)在是一名前端開發(fā)“工程師”。寫有《學(xué)習(xí)源碼整體架構(gòu)系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結(jié),已經(jīng)寫了7篇,點擊查看年度總結(jié)。
同時,最近組織了源碼共讀活動,幫助3000+前端人學(xué)會看源碼。公眾號愿景:幫助5年內(nèi)前端人走向前列。
識別上方二維碼加我微信、拉你進源碼共讀群
今日話題
略。分享、收藏、點贊、在看我的文章就是對我最大的支持~
總結(jié)
以上是生活随笔為你收集整理的面试官:请手写一个带取消功能的延迟函数,axios 取消功能的原理是什么的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分治法--线性时间选择
- 下一篇: PS教程第二十课:有了选区就有了界限