某次路过同事的工位,恰好看到同事在写面试评价,看到里面有一个问题:组件卸载时自动取消异步请求问题,不及格。node
我:???react
如今fetch已经支持手动abort请求了吗?面试
因而上网去查各类资料:how to abort fetch http request when component umounts
promise
而后获得的各类各样的资料里面,看起来比较靠谱的是这样一种:浏览器
componentDidMount(){
this.mounted = true;
this.props.fetchData().then((response) => {
if(this.mounted) {
this.setState({ data: response })
}
})
}
componentWillUnmount(){
this.mounted = false;
}
复制代码
我:????异步
就这样吗?函数
然而这个写法并无真的abort
掉fetch
请求,只是不去响应fetch成功以后的结果而已,这彻底没有达到取消异步请求的目的。fetch
因而我去问了问同事,如何真正abort
掉一个已经发送出去的fetch请求。ui
同事跟我说:如今浏览器还不支持abort
掉fetch
请求。this
我:……
同事继续:不过咱们能够经过Promise.race([cancellation, fetch()])
的方式,在fetch真正结束以前先调用cancellation
方法来返回一个reject
,直接结束这个Promise
,这样就能够看似作到abort
掉一个正在发送的fetch,至于真正的fetch
结果是怎么怎样的咱们就不须要管了,由于咱们已经获得了一个reject
结果。
我:那么有具体实现方法的wiki吗?
同事:咱们代码里面就有,你去看看就行。
我:……(我居然不知道!)
因而我就连读带问,认真研读了一下组件卸载自动取消异步请求的代码。
整个代码的核心部分确实是刚才同事提到的那一行代码:return Promise.race([cancellation, window.fetch(input, init)]);
不过这里的cancellation
实际上是另外一个Promise
,这个Promise
负责注册一个abort
事件,当咱们组件卸载的时候,主动触发这个abort
事件,这样最后若是组件卸载以前,fetch
请求已经响应完毕,就走正常逻辑,不然就由于咱们触发了abort事件返回了一个reject
的响应结果。
const realFetch = window.fetch;
const abortableFetch = (input, init) => {
// Turn an event into a promise, reject it once `abort` is dispatched
const cancellation = new Promise((_, reject) => {
init.signal.addEventListener(
'abort',
() => {
reject(abortError);
},
{ once: true }
);
});
// Return the fastest promise (don't need to wait for request to finish)
return Promise.race([cancellation, realFetch(input, init)]);
};
复制代码
那么咱们什么若是触发这个abort
事件呢,又根据什么去找到对应的fetch
请求呢?
首先为了绑定和触发咱们自定义的事件,咱们须要本身实现一套相似node里面的Emitter类,这个类只须要包含注册事件,绑定事件以及触发事件是哪一个方法便可。
export default class Emitter {
constructor() {
this.listeners = {};
}
dispatchEvent = (type, params) => {
const handlers = this.listeners[type] || [];
for(const handler of handlers) {
handler(params);
}
}
addEventListener = (type, handler) => {
const handlers = this.listeners[type] || (this.listeners[type] = []);
handlers.push(handler);
}
removeEventListener = (type, handler) => {
const handlers = this.listeners[type] || [];
const idx = handlers.indexOf(handler);
if(idx !== -1) {
handlers.splice(idx, 1);
}
if(handlers.length === 0) {
delete this.listeners[type];
}
}
}
复制代码
根据Emitter
类咱们能够衍生出一个Signal
类用做标记fetch
的类,而后一个SignalController
类做为Signal
类的控制器。
class AbortSignal extends Emitter {
constructor() {
super();
this.aborted = false;
}
toString() {
return '[AbortSignal]';
}
}
class AbortController {
constructor() {
super();
this.signal = new AbortSignal();
}
abort() {
this.signal.aborted = true;
this.signal.dispatchEvent('abort');
};
toString() {
return '[AbortController]';
}
}
复制代码
有了这两个类以后,咱们就能够去完善一下刚才的abortableFetch
函数了。
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
// These are necessary to make sure that we get correct output for:
// Object.prototype.toString.call(new AbortController())
AbortController.prototype[Symbol.toStringTag] = 'AbortController';
AbortSignal.prototype[Symbol.toStringTag] = 'AbortSignal';
}
const realFetch = window.fetch;
const abortableFetch = (input, init) => {
if (init && init.signal) {
const abortError = new Error('Aborted');
abortError.name = 'AbortError';
abortError.isAborted = true;
// Return early if already aborted, thus avoiding making an HTTP request
if (init.signal.aborted) {
return Promise.reject(abortError);
}
// Turn an event into a promise, reject it once `abort` is dispatched
const cancellation = new Promise((_, reject) => {
init.signal.addEventListener(
'abort',
() => {
reject(abortError);
},
{ once: true }
);
});
delete init.signal;
// Return the fastest promise (don't need to wait for request to finish)
return Promise.race([cancellation, realFetch(input, init)]);
}
return realFetch(input, init);
};
复制代码
咱们在传入的参数中加入加入一个signal
字段标识该fetch
请求是能够被取消的,这个signal
标识就是一个Signal
类的实例。
而后当咱们组件卸载的时候自动触发AbortController
的abort
方法,就能够了。
最后咱们改造一下Component
组件,给每个组件都内置绑定signal
的方法,当组件卸载是自动触发abort
方法。
import React from 'react';
import { AbortController } from 'lib/abort-controller';
/** * 用于组件卸载时自动cancel全部注册的promise */
export default class EnhanceComponent extends React.Component {
constructor(props) {
super(props);
this.abortControllers = [];
}
componentWillUnmount() {
this.abortControl();
}
/** * 取消signal对应的Promise的请求 * @param {*} signal */
abortControl(signal) {
if(signal !== undefined) {
const idx = this._findControl(signal);
if(idx !== -1) {
const control = this.abortControllers[idx];
control.abort();
this.abortControllers.splice(idx, 1);
}
} else {
this.abortControllers.forEach(control => {
control.abort();
});
this.abortControllers = [];
}
}
/** * 注册control */
bindControl = () => {
const controller = new AbortController();
this.abortControllers.push(controller);
return controller.signal;
}
_findControl(signal) {
const idx = this.abortControllers.findIndex(controller => controller.signal === signal);
return idx;
}
}
复制代码
这样,咱们全部继承自EnhanceComponent
的组件都会自带一个bindController
和abort
方法,咱们将bindController
生成的signal
传入fetch的参数就能够完成组件卸载是自动取消异步请求了。
import EnhanceComponent from 'components/enhance-component';
export default class Demo extends EnhanceComponent {
// ...
fetchData() {
util.fetch(UPLOAD_IMAGE, {
method: 'POST',
data: {},
signal: this.bindControl(),
})
}
// ...
}
复制代码