因為受 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 請求優先級的變化。
文章列表