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中断请求内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!