[React.js]组件卸载如何自动取消异步请求

栏目: IOS · Android · 发布时间: 6年前

内容简介:某次路过同事的工位,刚好看到同事在写面试评价,看到里面有一个问题:组件卸载时自动取消异步请求问题,不及格。我:???现在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;
}
复制代码

我:????

就这样吗?

然而这个写法并没有真的 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)]);
  }
  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 类的实例。

然后当我们组件卸载的时候自动触发 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(),
        })
    }
    // ...
}
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Linux设备驱动程序

Linux设备驱动程序

科波特 / 魏永明、耿岳、钟书毅 / 中国电力出版社 / 2006-1-1 / 69.00元

本书是经典著作《Linux设备驱动程序》的第三版。如果您希望在Linux操作系统上支持计算机外部设备,或者在Linux上运行新的硬件,或者只是希望一般性地了解Linux内核的编程,就一定要阅读本书。本书描述了如何针对各种设备编写驱动程序,而在过去,这些内容仅仅以口头形式交流,或者零星出现在神秘的代码注释中。 本书的作者均是Linux社区的领导者。Jonathan Corbet虽不是专职的内核......一起来看看 《Linux设备驱动程序》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

html转js在线工具
html转js在线工具

html转js在线工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具