AbortSignal:以前我沒得選,現在我想中止promise

卡頌發表於2021-10-06

大家好,我卡頌。

遙想數年前的一次面試,面試官問我: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.abortedtrue

// 監聽 abort 事件
signal.addEventListener('abort', () => {
  console.log("訊號中止!")
});

// 控制器中止訊號
controller.abort(); 

console.log('是否中止:', signal.aborted); 

如上程式碼呼叫後會依次列印:

  1. 訊號中止!
  2. 是否中止:true

在fetch中的應用

fetch API已經整合了AbortSignal

只需要將controller內的訊號物件作為signal引數傳給fetch

const controller = new AbortController();
fetch(url, {
  signal: controller.signal
});

當呼叫controller.abort()後,fetchpromise會變為AbortError DOMException reject

fetch('xxxx', {
  signal: controller.signal
}).then(() => {}, err => {
  if (err.name == 'AbortError') { 
    // 中止訊號
  } else {
    // 其他錯誤
  }
})

可以在此時處理中止後的操作。

這裡有個取消視訊下載Demo,可以看看fetch如何配合AbortSignal實現取消下載

與任何非同步操作結合

不僅是fetch,任何非同步操作只要符合如下規範,都可以與AbortError整合:

  1. AbortSignal(訊號物件)作為APIsignal引數傳入
  2. 約定如果API返回的promise變為AbortError DOMException reject則代表操作被中止
  3. 如果signal.aborted === true則立刻讓promise變為reject
  4. 觀測AbortSignal狀態的變化

如果API應用場景比較複雜(比如需要考慮多執行緒通訊),文件中提供了一套基於訂閱釋出abort-algorithms機制來完成步驟4。

總結

雖然AbortSignal原理很簡單,但只要遵守接入規範,他的可擴充套件性是很強的。

比如,可以將一個signal傳給多個符合規範的API,就能用一個控制器中止多個API的呼叫。

就像一個遙控器,同時操作家裡的空調、電視、洗衣機,你愛了麼?

歡迎加入人類高質量前端框架研究群,帶飛

相關文章