Как отменить несколько вызовов setState с помощью контроллера прерывания или отменяемых обещаний

#reactjs #react-native #setstate

#reactjs #react-native #setstate

Вопрос:

У меня есть эта init функция, которая вызывается в componentDidMount или других функциях, которые вызываются при взаимодействии с пользователем:

 protected async init() {
    if (this.props.requiredSource)
    ) {
      this.setState({ imageSource: this.props.requiredSource });  // task I want to cancel in componentWillUnmount;
      return;
    }

    // ... more code here ....

    const exists = await RNFS.exists(uri);
    if (exists) {
      this.cropImageForDisplayView(uri); // async that needs canceling in componentWillUnmount
      this.iAmUploading(); // async that needs canceling
      return;
    }

     this.setState({ imageSource: { uri: httpUri } }); // same here
     return;
    }
    try {
      // ...more code here...
      this.setState({ imageSource: { uri: `${httpUri.split('?')[0]}?w=${size.w}amp;h=${size.h}amp;min=1` } });  // same here
    } catch (e) {
      this.setState({ imageSource: { uri: httpUri } }); // same here
    }
  }

  private iAmUploading = async () => {
    if (this.props.tempFileId != null) {
      this.setState({ tempfileId: this.props.tempFileId }); // same here
    }
  };
 

Приведенный выше код нуждается в некотором рефакторинге, поскольку он генерирует некоторые классические update component that doesn't exist ошибки.

Мне нужен ответ о лучших методах использования контроллера прерывания или отменяемых обещаний для отмены нескольких вызовов setStates и асинхронных задач, когда компонент больше не существует.

Я изучил: https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html

но этой статье 5 лет, мне не очень понятно, является ли это лучшей практикой в наши дни и как я мог бы адаптировать это к отмене нескольких асинхронных задач и вызовов setState.

Любая помощь, которая не включает классический анти-шаблон this.isMounted , будет высоко оценена.

Примечание: текущее приложение написано с использованием классических компонентов (без перехватов), и рефакторинг в сторону перехватов на данный момент невозможен.

 "react": "16.13.1",
"react-dom": "16.11.0",
"react-native": "0.63.3",
 

Ответ №1:

  1. Стандартный подход, описанный в нескольких руководствах по реагированию (включая тот, на который дана ссылка в официальных документах react), заключается в установке флага при отключении компонента и проверке этого флага после каждого асинхронного шага вашего кода. Вероятно, это нормально для одного асинхронного вызова, но становится довольно подробным, если у вас есть несколько асинхронных вызовов, которые соединяются один за другим.
  2. Немного более чистое решение для этих более сложных случаев может быть достигнуто с помощью библиотеки, которая обеспечивает отмену из коробки. cancelable-promises Выше или bluebird предоставить реализации, совместимые с обещаниями, которые позволяют отменять.
  3. Другим подходом было бы переключение на использование API на основе Observable rxjs вместо promise-based. Наблюдаемые отменяются по дизайну. Недостатком является то, что вы не сможете использовать async / await с ними.

Ответ №2:

Вы можете использовать библиотеку, подобную cancelable-promise, чтобы сделать ваши обещания отменяемыми. Вот так:

 import { cancelable } from 'cancelable-promise';

protected async init() {
    // ... code here ....

    const exists = await RNFS.exists(uri);
    if (exists) {
      this.cancelableCropImageForDisplayView = cancelable(this.cropImageForDisplayView(uri));
      this.cancelableIAmUploading = cancelable(this.iAmUploading());
      return;
    }

    // ... code here ....
  }
 

Затем в componentWillUnmount вам просто нужно отменить свои обещания.

 componentWillUnmount() {
   this.cancelableCropImageForDisplayView.cancel();
   this.cancelableIAmUploading.cancel();
}
 

Имейте в виду, что ваши обещания все равно будут выполнены, но результат будет отброшен. Если вы действительно хотите отменить выполнение сразу после запуска componentWillUnmount, вам нужно убедиться, что ваши асинхронные вызовы поддерживают какой-то сигнал прерывания (что-то похожее на отмену Axios).

Ответ №3:

Если вы используете axios, вам необходимо сгенерировать токен отмены для каждого запроса, чтобы сделать его отменяемым.

Ссылка: https://codesandbox.io/s/cancel-requests-with-axios-reactjs-forked-6v9rf

Ответ №4:

Я пока не рекомендую следующий подход, потому что библиотека CPromise в настоящее время находится на стадии бета-тестирования, так что это просто для осведомленности.

Когда вы используете CPromise вместо native для построения цепочек обещаний, все асинхронные задачи, включая вложенные, становятся полностью отменяемыми / прерываемыми — при вызове метода cancel они будут отклонены по особой CanceledError причине. Внутренние асинхронные задания, построенные на обратных вызовах, также будут завершены, если пользовательский код поддерживает cancel обработку событий. Если вы используете асинхронные функции ECMA, вам необходимо подписать цепочку обещаний на внешний сигнал AbortController.

Демонстрация использования декораторов

 import { async, listen, cancel, timeout } from "c-promise2";
import cpFetch from "cp-fetch";

export class TestComponent extends React.Component {
  state = {
    text: ""
  };

  @timeout(5000)
  @listen
  @async
  *componentDidMount() {
    console.log("mounted");
    const response = yield cpFetch(this.props.url);
    // more tasks
    this.setState({ text: `json: ${yield response.text()}` });
  }

  render() {
    return <div>{this.state.text}</div>;
  }

  @cancel()
  componentWillUnmount() {
    console.log("unmounted");
  }
}
 

Вот пример, как написать отменяемый код (живая демонстрация):