JavaScript 中斷請求幾種方案詳解
1 Promise
Promise有一個缺點是一旦創建無法取消,所以本質上Promise是無法被終止的.
但是我們可以通過中斷調用鏈或中斷Promise來模擬請求的中斷.
中斷調用鏈
中斷調用鏈就是在某一個then/catch執行之後,後續的鏈式調用(包括then,catch,finally)不再繼續執行.
方法是在then/catch返回一個新的Promise實例,並保持pending狀態:
new Promise((resolve, reject) => { setTimeout(() => { resolve('result'); }); }).then(res => { // 達到某種條件,return一個pending狀態的Promise實例,以中斷調用鏈 if (res === 'result') { return new Promise(() => {}); } console.log(res); // 不打印 }).then(() => { console.log('then不執行'); // 不打印 }).catch(() => { console.log('catch不執行'); // 不打印 }).finally(() => { console.log('finally不執行'); // 不打印 });
中斷Promise
中斷Promise不等同於中止Promise,因為Promise是無法被終止的.
這裡的中斷指的是,在合適的時機,把pending狀態的promise給reject掉.例如一個常見的應用場景就是給網絡請求設置超時時間,一旦超時就中斷.
老規矩,用setTimeout來模擬網絡請求.閥值設置為Math.random() * 3000表示隨機3秒之內返回結果.
const request = new Promise((resolve, reject) => { setTimeout(() => { resolve('收到服務端數據') }, Math.random() * 3000) })
假設超過2秒就是網絡超時,我們可以封裝一個超時處理函數.
由於網絡請求所需的事件是隨機的,因此可以利用Promise.race方法,達到超時reject的目的.
const timeoutReject = (p1, timeout = 2000) => { const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject('網絡超時'); }, timeout); }); return Promise.race([p1, p2]); }; timeoutReject(request).then(res => { console.log(res); }).catch(err => { console.log(err); });
包裝abort方法——仿照Axios的CancelToken
上面實現的方式並不靈活,因為中斷Promise的方式有很多,不單單是網絡超時.
我們可以仿照Axios中CancelToken的核心源碼,簡單包裝一個abort方法,供使用者隨時調用.
function abortWrapper(p1) { let abort; const p2 = new Promise((resolve, reject) => { abort = reject; }); // 如果沒有resolve或reject,p2的狀態永遠是pending const p = Promise.race([p1, p2]); p.abort = abort; return p; } const req = abortWrapper(request); req.then(res => { console.log(res); }).catch(err => { console.log(err); }); setTimeout(() => { // 手動調用req.abort,將p2的狀態改變為rejected req.abort('手動中斷請求'); }, 2000);
如此封裝的主要目的就是為瞭能夠在Promise外部控制其resolve或reject,讓使用者可以隨時手動調用resolve(觸發.then)或reject(觸發.catch).
需要註意的是,雖然Promise請求被中斷瞭,但是promise並沒有終止,網絡請求依然可能返回,隻不過那時我們已經不關心請求結果瞭.
2 RXJS的unsubscribe方法
rxjs本身提供瞭取消訂閱的方法,即unsubscribe.
let stream1$ = new Observable(observer => { let timeout = setTimeout(() => { observer.next('observable timeout'); }, 2000); return () => { clearTimeout(timeout); } }); let disposable = stream1$.subscribe(value => console.log(value)); setTimeout(() => { disposable.unsubscribe(); }, 1000);
3 Axios的CancelToken
Axios的CancelToken有兩種使用方法:
- 方法一
import axios from 'axios'; const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios.get('/user/12345', { cancelToken: source.token }).catch(function (thrown) { if (axios.isCancel(thrown)) { console.log('Request canceled', thrown.message); } else { // handle error } }); source.cancel('Operation canceled by the user.');
- 方法二
import axios from 'axios'; const CancelToken = axios.CancelToken; // 創建一個變量如 cancel 用於存儲這個中斷某個請求的方法 let cancel; axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { cancel = c; // 將參數 c 賦值給 cancel }) }); // 判斷 cancel 是否為函數,確保 axios 已實例化一個CancelToken if (typeof cancel === 'function') { cancel(); cancel = null; }
CancelToken的核心源碼:(axios/lib/cancel/CancelToken.js)
'use strict'; var Cancel = require('./Cancel'); /** * A `CancelToken` is an object that can be used to request cancellation of an operation. * * @class * @param {Function} executor The executor function. */ function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); var token = this; executor(function cancel(message) { if (token.reason) { // Cancellation has already been requested return; } token.reason = new Cancel(message); resolvePromise(token.reason); }); } /** * Throws a `Cancel` if cancellation has been requested. */ CancelToken.prototype.throwIfRequested = function throwIfRequested() { if (this.reason) { throw this.reason; } }; /** * Returns an object that contains a new `CancelToken` and a function that, when called, * cancels the `CancelToken`. */ CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel }; }; module.exports = CancelToken;
可以看到,在Axios底層,CancelToken的核心源碼所體現的思想,與上面中斷Promise包裝abort方法的思想一致.
隻不過Axios在外部手動調用resolve(用戶觸發cancel方法),而resolve一旦調用,就會觸發promise的then方法,來看這個promise.then的源碼:(axios/lib/adapters/xhr.js)
if (config.cancelToken) { // Handle cancellation config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); // Clean up request request = null; }); }
可以看到then方法中會執行abort方法取消請求,同時調用reject讓外層的promise失敗.
到此這篇關於JavaScript 中斷請求幾種方案詳解的文章就介紹到這瞭,更多相關js中斷請求內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 項目中如何使用axios過濾多次重復請求詳解
- 前端取消請求及取消重復請求方式
- 淺談axios中取消請求及阻止重復請求的方法
- JS中promise特點與信任問題解決
- JavaScript前端超時異步操作完美解決過程