Введение
Проходя однажды мимо рабочей станции коллеги, я случайно увидел, как мой коллега пишет оценку интервью, и я увидел проблему: проблема с асинхронным запросом автоматически отменялась при удалении компонента, и это не удавалось.
Я:? ? ?
Поддерживает ли fetch запросы на прерывание вручную?
Поэтому я пошел в Интернет, чтобы искать все виды информации: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
Запрос просто не отвечает на результат после успешной выборки, что вообще не достигает цели отмены асинхронного запроса.
Поэтому я пошел спросить своих коллег, как на самом делеabort
Отбросьте запрос на выборку, который уже был отправлен.
Мне коллега сказал: браузер пока не поддерживаетabort
Терятьfetch
просить.
Я:……
Коллега продолжает: Но мы можем пройтиPromise.race([cancellation, fetch()])
способ, сначала позвоните, прежде чем выборка действительно закончитсяcancellation
метод для возвратаreject
Прямой конец этогоPromise
, так что кажется, чтоabort
отбросить отправляемую выборку, как для реальногоfetch
Нам не нужно заботиться о результате, потому что у нас естьreject
результат.
Я: Так есть ли вики с конкретными методами реализации?
Коллега: У нас это есть в коде, просто пойди и посмотри.
Я: ... (даже не знаю!)
Вот я и читал, и спрашивал, и внимательно изучал код, автоматически отменяющий асинхронный запрос при удалении компонента.
выполнить
Основная часть всего кода — это действительно строка кода, упомянутая коллегой только что:return Promise.race([cancellation, window.fetch(input, init)]);
Но здесьcancellation
На самом деле другойPromise
,этоPromise
ответственность за регистрациюabort
Событие, когда наш компонент выгружается, это активно срабатываетabort
событие, так что, наконец, если компонент был размонтирован ранее,fetch
На запрос был дан ответ, просто следуйте обычной логике, иначе он вернет событие прерывания, потому что мы инициировали событие прерывания.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
запрос?
Прежде всего, чтобы связать и вызвать наши пользовательские события, нам нужно реализовать набор классов 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(),
})
}
// ...
}