本文发布于639天前,最后更新于 637 天前,其中的信息可能已经有所发展或是发生改变。
const controller = new AbortController(); const signal = controller.signal; fetch(url, { signal }).then(response => { // Handle the response }).catch(error => { if (error.name === 'AbortError') { console.log('Request was cancelled'); } else { console.log('Request failed:', error); } }); // To abort the request, call the following: controller.abort();
js
给的api
让我们可以随时中断一个fetch
请求,那么,如果我想造轮子实现这个功能该怎么做呢?
Promise
首先能想到的是用一个
Promise
,来包装fetch
返回的promise
,再使用包装的Promise.reject
方法来实现中断请求不过函数返回时我使用了
Promise.race
而不直接返回abortPromise
,原因是因为fetch
可能返回异常,直接返回包装的promise
则可能产生无法捕获的异常
以下是示例代码
const fetchAbort = (url: string) => {
let res, abort;
const abortPromise = new Promise<Response>((resolve, reject) => {
res = resolve;
abort = reject.bind(this, `Fetch "${url}" has been aborted`);
});
return {
response: Promise.race([fetch(url).then(ret => res(ret)), abortPromise]),
abort
};
};
const { response, abort } = fetchAbort("./serverConfig.json");
abort();
// abort后,response的promise链将catch到fetch abort消息
// 而不会返回fetch返回的消息
response
.then(ret => ret.text())
.then(txt => console.log(txt))
.catch(err => {
console.log(err);
});
输出:
Fetch "./serverConfig.json" has been aborted
代码缺陷也显而易见,这段代码虽然能拦截fetch
的返回,但是fetch
占用的网络资源并不会被释放。
也就是说,如果api请求还在pending
状态,控制台network
列表仍然能看到fetch
还在请求状态,占用着io资源。
所以,如果想要abort
的同时,释放fetch
占用的资源,单靠Promise
是不够的。
XMLHttpRequest.abort()
可以使用XML
代替fetch
,反正造轮子就是为了绕开fetch
,封装就完了
const fetchAbort = (url: string) => {
let res, abort, xhr;
const xhrPromise = new Promise((resolve, reject) => {
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (this.status == 200) {
return resolve(this.response);
}
return reject({
xhr: this,
msg: "Error",
status: this.status,
statusText: this.statusText
});
}
};
xhr.open("Get", url);
xhr.send();
});
const abortPromise = new Promise((resolve, reject) => {
res = resolve;
abort = () => {
xhr.abort();
return reject(`Fetch "${url}" has been aborted`);
};
});
return {
response: Promise.race([xhrPromise.then(ret => res(ret)), abortPromise]),
abort
};
};
const { response, abort } = fetchAbort("./serverConfig.json");
abort();
response
.then(ret => console.log(ret))
.catch(err => {
console.log(err);
});
输出:
Fetch "./serverConfig.json" has been aborted
和fetch
的原生abort
代码对比,可以看到请求大小为0B,说明请求虽然发出去了,但是浏览器直接断开了连接,并没有选择接收。于是实现了一个简单的fetchAbort
不过封装程度还不够,剩下的轮子就不造了,了解原理才是本次造轮子的目的。