内容简介:某次路过同事的工位,刚好看到同事在写面试评价,看到里面有一个问题:组件卸载时自动取消异步请求问题,不及格。我:???现在fetch已经支持手动abort请求了吗?
某次路过同事的工位,刚好看到同事在写面试评价,看到里面有一个问题:组件卸载时自动取消异步请求问题,不及格。
我:???
现在fetch已经支持手动abort请求了吗?
于是上网去查各种资料: how to abort fetch http request when component umounts
然后得到的各种各样的资料里面,看起来比较靠谱的是这样一种:
componentDidMount(){ this.mounted = true; this.props.fetchData().then((response) => { if(this.mounted) { this.setState({ data: response }) } }) } componentWillUnmount(){ this.mounted = false; } 复制代码
我:????
就这样吗?
然而这个写法并没有真的 abort
掉 fetch
请求,只是不去响应fetch成功之后的结果而已,这完全没有达到取消异步请求的目的。
于是我去问了问同事,如何真正 abort
掉一个已经发送出去的fetch请求。
同事跟我说:现在浏览器还不支持 abort
掉 fetch
请求。
我:……
同事继续:不过我们可以通过 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)]); } return realFetch(input, init); }; 复制代码
那么我们什么如果触发这个 abort
事件呢,又根据什么去找到对应的 fetch
请求呢?
首先为了绑定和触发我们自定义的事件,我们需要自己实现一套类似node里面的Emitter类,这个类只需要包含注册事件,绑定事件以及触发事件是哪个方法即可。
emitter.js
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
类的控制器。
abort-controller.js
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
函数了。
abortable-fetch.js
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
方法。
enhance-component.js
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的参数就可以完成组件卸载是自动取消异步请求了。
xxxComponent.js
import EnhanceComponent from 'components/enhance-component'; export default class Demo extends EnhanceComponent { // ... fetchData() { util.fetch(UPLOAD_IMAGE, { method: 'POST', data: {}, signal: this.bindControl(), }) } // ... } 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 彻底卸载干净oracle 11g
- [译]如何从 Ubuntu 卸载 Redis
- 云设计模式之: 网关卸载模式
- 那些你应该考虑卸载的 VSCode 扩展
- Debian完全卸载清理MySQL服务器
- Mac中mongoDB的安装与卸载步骤详解
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。