文章出處

因為受 cancelable promise 的拖延,fetch 一直沒有傳統的 XHR 所擁有的 abort() 和 onprogress 功能,去年年底 cancelable promise 草案被徹底廢棄了,所以今年年初的這幾個月里,一份完全新的、fetch 專用的、不依賴 JS 規范、與 Promise 沒有任何直接關系的草案誕生了,而且 Firefox 已經率先實現了其中的大部分功能。

這份草案中引入了 3 種新的對象類型,FetchController(fetch 控制器)、FetchSignal(fetch 信號)、FetchObserver(fetch 觀察者),雖然這三個都是全局構造函數,但其實只有 FetchController 對象是真的需要你手動 new 出來的,其它兩種對象都是瀏覽器為你生成的,我們下面分別演示下如何中斷(取消、中止)一個 fetch 請求,以及如何監聽 fetch 請求的執行進度。

中斷 fetch 請求

const controller = new FetchController()
const signal = controller.signal

fetch("https://tc39.github.io/ecma262/", { signal }).then(() => {
  alert("請求成功")
}).catch(err => {
  if (err.name === "AbortError") {
    alert("請求被中斷") 
  }
})

setTimeout(() => controller.abort(), 500)

這段代碼中,我們先創建了一個 fetch 控制器 controller,每個控制器都自帶一個 fetch 信號對象(signal 屬性),然后在你用 fetch() 發送請求的時候把這個信號對象帶上(signal 參數)。之后,你就可以通過控制器的 abort() 方法,來中斷那個 fetch 請求了。只要在你執行 abort() 方法的時候,那個 fetch 請求還沒有執行完畢(請求還沒發出去、請求發出去了還沒接收到響應、響應還沒接收完),那么當初 fetch() 返回的那個 promise 就會被 reject,所產生的 error 對象的 name 為 AbortError。

上面這個例子中,我是在 500 毫秒的時候中斷了這個請求,這是個絕對的數字,所以根據你的網速不同,執行代碼時你有可能會看到彈出“請求成功”,也有能看到“請求被中斷”。

一個信號對象還可以同時傳遞給多個 fetch 請求:

const controller = new FetchController()
const signal = controller.signal

fetch("https://tc39.github.io/ecma262/", { signal })
fetch("https://fetch.spec.whatwg.org/", { signal })
fetch("https://dom.spec.whatwg.org/", { signal })

controller.abort() // 同時中斷三個請求

信號對象有一個 aborted 屬性表明自己是否已經被中斷過了,一旦被中斷,它不可能回到當初的狀態,也就是說,你不能二次利用一個已經被中斷過的信號對象,把這樣的信號對象傳給 fetch() 的話,fetch() 會立即結束,不會發出請求:

const controller = new FetchController()
const signal = controller.signal

controller.abort()

alert(signal.aborted) // true

fetch("https://tc39.github.io/ecma262/", { signal }) // 直接被 reject,請求不會發出

信號對象上還可以監聽 abort 事件,不過我想一般用不上,因為請求中斷后的處理代碼一般寫在 fetch(...).catch(...) 里面:

const controller = new FetchController()
const signal = controller.signal

signal.addEventListener("abort", function(e) { // 使用 signal.onabort = 注冊監聽函數也可以
  alert(e.type) // abort
  alert(signal.aborted) // true
})

alert(signal.aborted) // false

controller.abort()

一個控制器除了可以讓自己的信號對象 abort,還可以關注(跟隨、監聽)一個別的控制器的信號對象,只要關注的這個信號對象被 abort 了,自己的 abort() 方法就會被自動執行,從而自己的信號對象也會被 abort:

const fc1 = new FetchController()
const fc2 = new FetchController()

fc1.follow(fc2.signal)

fc2.abort()

alert(fc1.signal.aborted) // true,即便你沒有手動執行 fc1.abort()

再來看個更復雜的例子:

const fc1 = new FetchController()
const fc2 = new FetchController()
const fc3 = new FetchController()

fc1.follow(fc2.signal)
fc2.follow(fc3.signal)

fetch("https://tc39.github.io/ecma262/", { signal: fc1.signal })
fetch("https://fetch.spec.whatwg.org/", { signal: fc2.signal })
fetch("https://dom.spec.whatwg.org/", { signal: fc3.signal })

fc3.abort() //  三個請求都中斷了

一個控制器只能關注一個別人家的信號對象,而一個信號對象可以被任意多個別家的控制器關注。不能直接或間接的關注自己的信號對象,比如上面的代碼中再增加一行 fc3.follow(fc1.signal) 就會產生環形關注,所以那句代碼會沒有任何效果。還有一個 unfollow() 方法可以取消關注。

總結下就是,一個控制器對象有一個 signal 屬性指向一個信號對象,還有 abort()、follow()、unfollow() 這三個方法。一個信號對象有一個 aborted 屬性,還有一個 onabort 事件監聽函數。

監聽 fetch 請求

上面說到了 FetchController 和 FetchSignal,接下來要說說 fetch 觀察者 - FetchObserver 對象。fetch 觀察者對象上可以監聽 statechange、requestprogress、responseprogress 三種事件,分別用來獲取 fetch 請求的狀態信息變化、請求數據發送的大小變化(POST 請求)、響應數據接收的大小變化信息,先來看看如何監聽狀態信息的變化:

fetch("https://tc39.github.io/ecma262/", {
  observe(observer) {
    observer.onstatechange = () => { // 也可以用 addEventListener("statechange"...
      alert(observer.state) // "requesting"、"responding"、"aborted", "errored"、"complete" 中的某個值
    }
  }
})

FetchObserver 對象不用你手動 new,瀏覽器會為你 new 一個,在 fetch 請求開始之前,通過你事先指定的 observe 回調函數的參數傳遞給你。然后你在這個觀察者對象上注冊 statechange 監聽函數,就可以在請求的不同階段執行特定的代碼了。

監聽響應的接收進度使用 responseprogress 事件:

fetch("https://img.alicdn.com/tfscom/TB1y6icQXXXXXaQXFXXXXXXXXXX.mp4", {
  observe(observer) {
    observer.onresponseprogress = e => { 
      if(e.lengthComputable) {
        console.log("視頻下載進度:" + parseInt(e.loaded / e.total * 100) + "%")
      }
    }
  }
})

監聽 POST 數據的發送進度使用 requestprogress 事件:

fetch("/upload", {
  method: "POST",
credentials: "include", body: canvas.toBlob(), observe(observer) { observer.onrequestprogress
= e => { console.log("圖片上傳進度:" + parseInt(e.loaded / e.total * 100) + "%") } } })

在本文發布的現在,規范草案還只是最初的階段,可能有很多細節沒考慮,目前 Firefox 也沒有實現我上面所說的所有功能,實現了的也有一些 bug,而且還需要事先在 about:config 手動添加某兩個選項才可以使用,所以本文僅僅是簡要介紹,大家暫時眼睛看看就夠了。

以后 fetch 控制器的功能可能還會增加,不僅僅能讓一個 fetch 請求中斷,比如還可以指定這個請求的優先級,fetch 觀察者同時也可以用來監聽 fetch 請求優先級的變化。


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()