前端如何取消介面呼叫

林恒發表於2024-07-10

🧑‍💻 寫在開頭

點贊 + 收藏 === 學會🤣🤣🤣

1. xmlHttpRequest是如何取消請求的?

例項化的XMLHttpRequest物件上也有abort方法

const xhr = new XMLHttpRequest();
xhr.addEventListener('load', function(e) {
  console.log(this.responseText);
});
xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
xhr.send();
// 返回
{
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}

如果在send後直接abort取消

// xhr的取消操作:執行過程比較模糊,不知道abort什麼時機進行處理
xhr.abort()

2. AbortController

const ac = new AbortController();
const { signal } = ac;
const url = "https://jsonplaceholder.typicode.com/todos/1";
​
fetch(url, { signal })
  .then((res) => res.json())
  .then((json) => console.log(json));

直接使用abort取消請求

ac.abort()

這裡報錯的原因是沒有對錯誤進行捕獲

// 修改後的程式碼
fetch(url, { signal: ac.signal })
  .then((res) => res.json())
  .then((json) => console.log(json))
  .catch(e => console.log(e)) // DOMException: signal is aborted without reason
ac.abort() // abort接受一個入參,會被傳遞到signal的reason屬性中

為什麼可以這樣取消?

fetch監聽signal物件的狀態,進而可以終止請求

2.1 如何同時取消多個請求?

const ac = new AbortController();
const { signal } = ac;
const url = "https://jsonplaceholder.typicode.com/todos";
​
const todoRequest = (id, { signal }) => {
  fetch(`${url}/${id}`, { signal })
    .then((res) => res.json())
    .then((json) => console.log(json))
    .catch((e) => console.log(e)); // DOMException: signal is aborted without reason
};
​
todoRequest(1, { signal });
todoRequest(2, { signal });
todoRequest(3, { signal });
​
ac.abort("cancled");

2.2 AbortSignal

是一個介面,用於表示一個訊號物件,它允許你與正在執行的非同步操作通訊,以便可以在操作完成之前將其中止。

2.3 AbortSignal的方法

2.3.1 abort

靜態方法,用於建立一個已經中止的 AbortSignal 物件。當你呼叫這個方法時,它會返回一個帶有 aborted 狀態為 trueAbortSignal 例項。

const signalAbout = AbortSignal.abort(); // AbortSignal {aborted: true, reason: DOMException: signal is aborted without reason...}

2.3.2 throwIfAborted 方法

用於在執行程式碼之前檢查 AbortSignal 是否已經被中止。如果 AbortSignal 已經被中止,它會丟擲一個 AbortError。這個方法可以幫助開發者在執行特定操作之前確保沒有被中止,以避免不必要的處理。

const signalAbout = AbortSignal.abort('abortedReason');
try {
    signalAbout.throwIfAborted(); // 丟擲error: abortedReason
} catch (error) {
    console.log(error);
}

2.3.3 timeout

用於建立一個在指定時間後自動中止的 AbortSignal 物件。這在需要設定請求超時時非常有用。

// 使用 AbortSignal.timeout 設定 10ms超時
const signalAbout = AbortSignal.timeout(10);
const todoRequest = (id, { signal }) => {
  fetch(`${url}/${id}`, { signal })
    .then((res) => res.json())
    .then((json) => console.log("json: ", json))
    .catch((e) => console.log("err: ", e)); //DOMException: signal timed out 
};
todoRequest(1, { signal: signalAbout });

2.3.3.1 新增事件監聽 => 從沒有終止到被終止

AbortSignal繼承自EventTarget,因為 AbortSignal 是用來監聽 abort 事件的,而 EventTarget 提供了新增、移除和觸發事件監聽器的機制。

const signalAbout = AbortSignal.timeout(10);
signalAbout.addEventListener("abort", (e) => {
    console.log("aborted: ", e);
})
​

e的列印如下:

3. 實現一個主動取消的promise

const ac = new AbortController();
const { signal } = ac;
​
const cancelablePromise = ({signal}) => 
    new Promise((resolve, reject) => {
        // 情況1:直接主動取消
        signal?.throwIfAborted(); // 也可以用reject
​
        // 情況2:正常處理業務邏輯
​
        setTimeout(() => {
            Math.random() > 0.5 ? resolve('data') : reject('fetch error');
        }, 1000);
​
        // 情況3:超時 todo?
​
        // 監聽取消
        signal.addEventListener("abort", () => {
            reject(signal?.reason);
        });
    })
// 發起網路請求
cancelablePromise({signal})
.then(res => console.log('res: ', res))
.catch(err => console.log('err: ', err))
// 情況1 
// ac.abort('使用者離開頁面了') // err:  使用者離開頁面了
​
// 情況2 正常請求 err:  fetch error || res:  data

4. 如何使用signal取消事件監聽?

當對一個元素新增了多個事件監聽,不需要像removeEventListener一樣,每個事件都需要取消一次,每次都要寫明對應事件的事件控制代碼

使用signal 只需要取消一次訊號,全部事件監聽都被取消

const ac = new AbortController();
const { signal } = ac;
const eleA = document.querySelector('#a');
const eleB = document.querySelector('#b');
​
function aEleHandler () {}; // 事件
eleA.addEventListener('click', aEleHandler, {signal}); // 無論繫結多少個事件,都只需要一個signal
​
eleB.addEventListener('click', () => {
    ac.abort(); // 只需要取消一次
})

5. 請求多個介面進行資料組裝的場景

當網速不好的時候,如何取消這種不斷進行的網路請求?

const ac = new AbortController();
const { signal } = ac;
const fetchAndRenderAction = (signal) => {
    requestData(signal); // 多個序列或者並行的介面
    drawAndRender(signal); // 非同步渲染
}
​
try{
    fetchAndRenderAction({signal})
}catch{
    // dosomething...
}

6. 總結

對於使用者主動離開頁面,或者使用者的網路很卡的時候(預期返回順序是:介面1 => 介面2;但是介面1返回太慢,導致順序混亂。)這就需要手動終止請求。建構函式AbortController的例項訊號量signal(可以作為ref儲存起來),signal作為fetch的引數,在每次請求的時候,可以手動呼叫abort方法,取消上一次的請求。

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文件,大家一起討論學習,一起進步。

前端如何取消介面呼叫

相關文章