博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[React.js]组件卸载如何自动取消异步请求
阅读量:6909 次
发布时间:2019-06-27

本文共 5679 字,大约阅读时间需要 18 分钟。

背景介绍

某次路过同事的工位,刚好看到同事在写面试评价,看到里面有一个问题:组件卸载时自动取消异步请求问题,不及格。

我:???

现在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;}复制代码

我:????

就这样吗?

然而这个写法并没有真的abortfetch请求,只是不去响应fetch成功之后的结果而已,这完全没有达到取消异步请求的目的。

于是我去问了问同事,如何真正abort掉一个已经发送出去的fetch请求。

同事跟我说:现在浏览器还不支持abortfetch请求。

我:……

同事继续:不过我们可以通过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类,这个类只需要包含注册事件,绑定事件以及触发事件是哪个方法即可。

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类的实例。

然后当我们组件卸载的时候自动触发AbortControllerabort方法,就可以了。

最后我们改造一下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的组件都会自带一个bindControllerabort方法,我们将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(),        })    }    // ...}复制代码

转载地址:http://rofcl.baihongyu.com/

你可能感兴趣的文章
hive复杂类型与java类型的对应
查看>>
[Ubuntu] ubuntu10.04系统维护之Wine的安装
查看>>
iOS获取UIView上某点的颜色值
查看>>
cocos2d-x 3.0 android mk文件 之 自己主动遍历*.cpp文件
查看>>
python数字图像处理(7):图像的形变与缩放
查看>>
设计模式-观察者模式(上)<转>
查看>>
RabbitMQ 集群与高可用配置
查看>>
Android IOS WebRTC 音视频开发总结(六二)-- 大数据解密国外实时通讯行业开发现状...
查看>>
openvas
查看>>
消息推送技术
查看>>
flume 自己定义 hbase sink 类
查看>>
组织目标与个人目标
查看>>
Educational Codeforces Round 8 E. Zbazi in Zeydabad 树状数组
查看>>
自己主动下载源代码_并编译_打包_部署_重新启动服务的Shell脚本
查看>>
常思己过 如切如磋
查看>>
Android中使用Handler造成内存泄露的分析和解决
查看>>
《ArcGIS Engine+C#实例开发教程》第六讲 右键菜单添加与实现
查看>>
ArrayList与LinkedList区别
查看>>
Linux 学习之路:认识shell和bash
查看>>
POJ 3041(最小点覆盖)
查看>>