「取消擷取」的原始 GitHub 問題是 自 2015 年開業至今現在,如果我在 2015 年外出 (今年) 拿到 2 號,這表示 因為 2015 年其實就是「永遠」。
2015 年是我們第一次開始嘗試停止執行中的擷取作業,而在 780 個 GitHub 註解之後 分別提出幾個 false 啟動、5 提取要求,我們終於在瀏覽器中擷取到達目的地, 第一個是 Firefox 57
最新資訊:這不對,是錯了。Edge 16 號登陸月球支援先鋒!恭喜 邊緣隊!
我會先來深入介紹歷史,但首先是 API:
控制器 + 信號操縱
認識 AbortController
和 AbortSignal
:
const controller = new AbortController();
const signal = controller.signal;
控制器只有一個方法:
controller.abort();
您執行這些動作時,會通知訊號:
signal.addEventListener('abort', () => {
// Logs true:
console.log(signal.aborted);
});
這個 API 是由 DOM 標準提供,而這是整個 API。是 是通用的,適合其他網路標準和 JavaScript 程式庫使用。
取消信號並擷取
擷取作業可能需要 AbortSignal
時間。舉例來說,以下是設定擷取逾時時間滿 5 次
秒:
const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => controller.abort(), 5000);
fetch(url, { signal }).then(response => {
return response.text();
}).then(text => {
console.log(text);
});
取消擷取後,系統會取消要求和回應,因此任何讀取回應主體的行為
也會取消 (例如 response.text()
)。
這裡提供了示範 - 截至本文撰寫時間, 支援支援 Firefox 57此外,沒有人能學習任何設計技巧,請盡情觀看影片 示範過程中
或者,您可以為要求物件指定信號,然後在之後傳遞給擷取:
const controller = new AbortController();
const signal = controller.signal;
const request = new Request(url, { signal });
fetch(request);
因為 request.signal
是 AbortSignal
所以可以運作。
回應已取消的擷取作業
取消非同步作業時,承諾會拒絕含有名為 AbortError
的 DOMException
:
fetch(url, { signal }).then(response => {
return response.text();
}).then(text => {
console.log(text);
}).catch(err => {
if (err.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Uh oh, an error!', err);
}
});
您通常不會希望在使用者取消作業時顯示錯誤訊息,因為這項操作並非 「錯誤」就能順利完成使用者要求的動作為避免這種情況發生,請使用 if-陳述式,例如 專門處理取消錯誤。
以下範例說明如何透過按鈕載入內容,以及讓使用者取消的按鈕。如果擷取 系統會顯示錯誤,但「除非」是取消錯誤:
// This will allow us to abort the fetch.
let controller;
// Abort if the user clicks:
abortBtn.addEventListener('click', () => {
if (controller) controller.abort();
});
// Load the content:
loadBtn.addEventListener('click', async () => {
controller = new AbortController();
const signal = controller.signal;
// Prevent another click until this fetch is done
loadBtn.disabled = true;
abortBtn.disabled = false;
try {
// Fetch the content & use the signal for aborting
const response = await fetch(contentUrl, { signal });
// Add the content to the page
output.innerHTML = await response.text();
}
catch (err) {
// Avoid showing an error message if the fetch was aborted
if (err.name !== 'AbortError') {
output.textContent = "Oh no! Fetching failed.";
}
}
// These actions happen no matter how the fetch ends
loadBtn.disabled = false;
abortBtn.disabled = true;
});
這裡提供了試用版 - 截至本文撰寫時間為止,只有 支援 Edge 16 和 Firefox 57
一個信號,擷取多個信號
單一信號可用來一次取消多個擷取作業:
async function fetchStory({ signal } = {}) {
const storyResponse = await fetch('/story.json', { signal });
const data = await storyResponse.json();
const chapterFetches = data.chapterUrls.map(async url => {
const response = await fetch(url, { signal });
return response.text();
});
return Promise.all(chapterFetches);
}
在上述範例中,同一個信號用於初始擷取以及平行章節
擷取。使用 fetchStory
的方法如下:
const controller = new AbortController();
const signal = controller.signal;
fetchStory({ signal }).then(chapters => {
console.log(chapters);
});
在此情況下,呼叫 controller.abort()
將取消任何進行中的擷取作業。
未來
其他瀏覽器
Edge 很榮幸能先推出這艘船,而 Firefox 就在步道上很熱。工程師 透過測試套件導入,但規格先前為 寫入的內容如使用其他瀏覽器,請參考以下支援單:
在 Service Worker 中
我必須完成 Service Worker 零件的規格,但計費方式如下:
如前所述,每個 Request
物件都有 signal
屬性。在 Service Worker 中
如果網頁不再對回應內容感興趣,fetchEvent.request.signal
就會中止訊息。
因此,類似下方的程式碼應該可以正常運作:
addEventListener('fetch', event => {
event.respondWith(fetch(event.request));
});
如果網頁取消擷取作業,fetchEvent.request.signal
信號就會中止,因此擷取
Service Worker 也會取消。
如果您要擷取 event.request
以外的內容,則必須將訊號傳遞給
進行這項操作。
addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (event.request.method == 'GET' && url.pathname == '/about/') {
// Modify the URL
url.searchParams.set('from-service-worker', 'true');
// Fetch, but pass the signal through
event.respondWith(
fetch(url, { signal: event.request.signal })
);
}
});
依照規格追蹤這個問題,我要將連結加到 預先準備好實作內容
歷史
對...這個相對簡單的 API 要花很長的時間才能整合完成。原因如下:
API 異議
如您所見,GitHub 討論內容相當冗長。
這個執行緒中存在許多細微差異 (且有些細微差異),但主要的異同之處在於
群組希望 abort
方法存在於 fetch()
傳回的物件上,而
希望取得回應與影響回應之間的差異。
這些需求互不相容,因此一組無法達成期望的目標。如果是,
不好意思,抱歉!只要感覺有所好,我也包含在那群人中。但AbortSignal
可以滿足
其實這是合適的選擇此外,透過鏈結的承諾
會變得非常複雜
如要傳回提供回應的物件,但也可以取消,則可以建立 簡易包裝函式:
function abortableFetch(request, opts) {
const controller = new AbortController();
const signal = controller.signal;
return {
abort: () => controller.abort(),
ready: fetch(request, { ...opts, signal })
};
}
在 TC39 中發生否定
系統無法將取消的動作與錯誤區分完成。這也包含第三個承諾 狀態來表示「已取消」,以及處理同步處理和非同步作業中取消作業的新語法 程式碼:
非實際程式碼 - 提案已撤銷
try { // Start spinner, then: await someAction(); } catch cancel (reason) { // Maybe do nothing? } catch (err) { // Show error message } finally { // Stop spinner }
取消動作時最常執行的操作是一件,以上述方式分隔的提案
因此您不需要專門處理取消錯誤。catch cancel
敲定
您會聽到已取消的動作,但在大多數情況下,您不需要這麼做。
這樣就到 TC39 的第 1 階段了,但是沒有達成共識,也已撤回提案。
我們的替代提案「AbortController
」不需要任何新語法,因此不太合理
符合 TC39 的規格JavaScript 的所有必要功能都已存在,因此我們定義了
網路平台中的介面,尤其是 DOM 標準。我們做出決定後
其餘部分很快就團結起來
大幅變更規格
「XMLHttpRequest
」多年來已經取消,但規格相當模糊。天氣不佳
也會提示可能避免或終止基礎網路活動
呼叫 abort()
與擷取完成之間存在競爭狀況。
我們希望這次的結果是正確的,但也產生了大幅度的規格變更,需要進行大量操作 評論 (這是我的錯誤,非常感謝 Anne van Kesteren 和 Domenic Denicola,以拖曳的方式將我拉過來) 以及一系列精良的考試。
我們終於來到這裡了!我們擁有可以取消非同步動作的全新網頁基本功能,而多個擷取作業可以 就能同時控制!在下方的章節中,我們會說明如何在擷取的整個生命週期中啟用優先順序變更,以及更高層級的設定 用於觀察擷取進度的 API。