超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

如何取消 Web 请求

在有些场景下可能需要用到取消请求的操作,比如在同一时刻触发多次请求,只保留最新的请求;比如表单提交中关闭页面等行为,本文加少一些常用的触发 Web 请求的方式如何取消。

AbortControllor

AbortController 接口表示一个控制器对象,该对象允许您在需要时中止一个或多个 Web 请求。

AbortController 分别有一个属性 AbortController.signal 和一个方法 AbortController.abort,他们结合起来可以用来终止 Web 请求。

它的使用过程如下:

首先创建一个 AbortController 对象 controller,然后在调用 fetch 时,传入 controller.signal 作为选项,此时控制器和请求被关联在一起,使用控制器的 controller.abort 方法,即刻终止此请求。

const controller = new AbortController();

function download() {
return fetch(url, { signal: controller.signal });
}

function abort() {
controller.abort();
}

注意,当 controller.abort 被调用时, fetch 返回的 promise 会进入 reject 状态并返回一个名为 AbortError 的 DOMException。

AbortController.signal 搭配 AbortController.abort 还可以用来响应一些取消类的行为:

const controller = new AbortController();
controller.signal.addEventListener('abort', (err) => {
console.log('aborted!');
});
controller.abort();

// 输出
aborted!

注意,AbortControllor 只对浏览器原生的 fetch 生效,在日常开发中经常使用其它的工具如 axios,下一节看一下 axios 怎么取消。

axios 请求取消

axios 提供了 cancelToken 配置项可以用来取消其请求,其使用和 AbortControllor 很相似,看下面的例子:

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
}
});

// cancel the request (the message parameter is optional)
source.cancel();

也可以给 CancelToken 传入一个执行器来执行:

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c;
})
});

// cancel the request
cancel();

都写到这里了,顺便看看 axios 内部是怎么实现取消的:

首先看 cancelToken 相关的代码:

// 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;
});
}

可以看到当 cancelToken.promise 调用 resolve 时进行了取消,结合它的使用不难猜测 cancelToken.source().cancel 和 传入 CancelToken executor 返回给用户的方法一定用来是 resolve cancelToken.promise 的,接着找相关代码:

// lib/cancel/CancelToken.js
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};

从上面看出 axios 的第一个示例 cancelToken.source 方法就是是调用了 CancelToken 构造器,也就是上面 axios 示例的第二种写法,后面就没必要看了,继续找构造器相关的代码就知道做了什么:

// lib/cancel/CancelToken.js
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);
});
}

很明显可以看出 this.promise 就是 config.cancelToken.promise.then,而 executor 就是 new CancelToken 时传入的方法,最终暴露给外面了 function cancel 这个方法,而这个方法就是用来 config.cancelToken.promise,和前面的猜测一致,不在多说。

同样的,使用同一个 cancelToken 也可以取消多个请求。

中止 Promise

Promise 也可以手动中止,只需要在创建 Promise 时,对外暴露 reject

let abort;
let p = new Promise((resovle, reject) => {
abort = err => reject(err);
// do sth;
});

abort();

XHR 请求取消

XHR 原生提供了 abort 方法,可以直接取消:

let xhr = new XMLHttpRequest();
xhr.onerror = (err) => console.log(err);
xhr.onabort = () => console.log('aborted!');
xhr.open('get', 'http://baidu.com');
xhr.send();

setTimeout(() => {
xhr.abort();
}, 100);

注意,和其他不同的是,xhr 取消不会触发 error 事件,而是触发 abort 事件。

参考