AbortSignal:以前我沒得選,現在我想中止Promise
大家好,我卡頌。
遙想數年前的一次面試,面試官問我:promise有什么缺點?
真是百思不得姐啊...
答案是:promise一旦初始化,就不能中止。這是由promise的實現決定的。
AbortSignal的出現使promise從語義上變為可中止的。并且,只要符合規范,所有異步操作都能變為「可中止的」。
AbortSignal是什么
AbortSignal是個實驗性API,不過兼容性還不錯,而且polyfill實現起來也不復雜。
AbortSignal可以實例化一個「信號對象」(signal object)。
AbortController可以實例化一個「信號對象」的控制器。
就像遙控器可以發出信號關電視一樣,AbortController的實例可以控制中止信號。
只要符合AbortSignal的接入規范,任何異步操作都能實現中止功能。
舉個例子,首先new一個控制器實例:
- // 控制器實例
- const controller = new AbortController();
- const signal = controller.signal;
其中signal是控制器對應的「信號對象」。
「信號對象」可以監聽abort事件,當信號被中止時被觸發。
調用controller.abort()方法后會中止信號,此時signal.aborted為true。
- // 監聽 abort 事件
- signal.addEventListener('abort', () => {
- console.log("信號中止!")
- });
- // 控制器中止信號
- controller.abort();
- console.log('是否中止:', signal.aborted);
如上代碼調用后會依次打印:
- 信號中止!
- 是否中止:true
在fetch中的應用
fetch API已經集成了AbortSignal。
只需要將controller內的「信號對象」作為signal參數傳給fetch:
- const controller = new AbortController();
- fetch(url, {
- signal: controller.signal
- });
當調用controller.abort()后,fetch的promise會變為AbortError DOMException reject:
- fetch('xxxx', {
- signal: controller.signal
- }).then(() => {}, err => {
- if (err.name == 'AbortError') {
- // 中止信號
- } else {
- // 其他錯誤
- }
- })
可以在此時處理中止后的操作。
這里有個取消視頻下載Demo[1],可以看看fetch如何配合AbortSignal實現取消下載
與任何異步操作結合
不僅是fetch,任何異步操作只要符合如下規范,都可以與AbortError集成:
- 將AbortSignal(信號對象)作為API的signal參數傳入
- 約定如果API返回的promise變為AbortError DOMException reject則代表操作被中止
- 如果signal.aborted === true則立刻讓promise變為reject
- 觀測AbortSignal狀態的變化
如果API應用場景比較復雜(比如需要考慮多線程通信),文檔中提供了一套基于「訂閱發布」的abort-algorithms[2]機制來完成步驟4。
總結
雖然AbortSignal原理很簡單,但只要遵守接入規范,他的可擴展性是很強的。
比如,可以將一個signal傳給多個符合規范的API,就能用一個控制器中止多個API的調用。
就像一個遙控器,同時操作家里的空調、電視、洗衣機,你愛了么?
參考資料
[1]取消視頻下載Demo:
https://mdn.github.io/dom-examples/abort-api/[2]abort-algorithms:
https://dom.spec.whatwg.org/#abortsignal-abort-algorithms