Как отменить повторяющееся обещание (рекурсивные функции) в React, когда компонент размонтирован?

Всем привет

Я использую React Native и хочу добавить некоторую функцию, с помощью которой пользователь может импортировать несколько файлов в мое приложение, и пользователь может отменить процесс импорта в любое время.

Но когда пользователь импортирует эти файлы, я хочу импортировать их один за другим, чтобы сообщить пользователю, какие файлы были успешно импортированы, а какие файлы не были импортированы,

это важно для меня, потому что я хочу сообщить пользователю, сколько файлов, которые были выбраны, успешно импортированы, а также полезно для отображения каждого файла в пользовательском интерфейсе, когда этот файл импортируется, и для этого мне нужно использовать Recursive Functions,

Проблема в том, что я понятия не имею, как отменить Promise, который использует Recursive Functions, Я пытаюсь использовать метод makeCancelable с сайта реакции, он не работает, и я думаю, что он просто отменяет Promise в верхней части дерева Recursive Functions, а не все выполнено Обещание. Кроме того, я не хочу использовать какие-либо deps/packages, если это возможно. Есть идеи?

Основные инструменты

Использование реального устройства Xiaomi Redmi 1S 4.4 Kitkat

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

Пример кода

importFiles.js

import RNFetchBlob from 'rn-fetch-blob';
import CameraRoll from '@react-native-community/cameraroll';
import _ from 'lodash';

const fs = RNFetchBlob.fs;

/**
 * Import directory destination
 */
const dest = `${fs.dirs.SDCardDir}/VEGA/.src/`;

/**
 * An increment index to tell the function which index to run
 */
let i = 0;

/**
 * Import the files to this App with some encryption
 * @param {object} config
 * @param {string} config.albumId
 * @param {[{
 *  uri: string,
 *  mimeType: string,
 *  albumName: string,
 *  timestamp: number,
 *  isSelected: boolean,
 * }]} config.files
 * @param {'fake' | 'real'=} config.encryptionMode
 */
const importFiles = config => {
  return new Promise(async (resolve, reject) => {
    const {albumId, files, encryptionMode} = config;

    if (_.isEmpty(files) || !_.isArray(files)) {
      reject('invalid files');
      return;
    }

    const file = files[i];

    /**
     * It's mean Done when the file got "undefined"
     */
    if (!file) {
      resolve();
      return;
    }

    const uri = file.uri.replace('file://', '');

    try {
      /**
       * Fake Encryption
       *
       * It's fast but not totally secure
       */
      if (!encryptionMode || encryptionMode === 'fake') {
        const md5 = await fs.hash(uri, 'md5');
        const importedFileUri = `${dest}.${md5}.xml`;

        /**
         * TODO:
         * * Test cancelable
         */
        await fs.readFile(uri, 'base64');
        // await fs.mv(uri, importedFileUri);
        // await CameraRoll.deletePhotos([uri]);

        /**
         * If successfully import this file then continue it to
         * the next index until it's "undefined"
         */
        i++;
      }

      /**
       * Real Encryption
       *
       * It's slow but totally secure
       */
      if (encryptionMode === 'real') {
      }

      await importFiles({files, encryptionMode}).promise;
      resolve();
    } catch (error) {
      reject(error);
    }
  });
};

export default importFiles;

FileImporter.js (Как я использую метод makeCancelable)

import React, {useEffect} from 'react';
import {View, Alert} from 'react-native';
import {Contrainer, TopNavigation, Text} from '../components/Helper';
import {connect} from 'react-redux';
import utils from '../utils';

const makeCancelable = promise => {
  let hasCanceled_ = false;

  const wrappedPromise = new Promise((resolve, reject) => {
    promise.then(
      val => (hasCanceled_ ? reject({isCanceled: true}) : resolve(val)),
      error => (hasCanceled_ ? reject({isCanceled: true}) : reject(error)),
    );
  });

  return {
    promise: wrappedPromise,
    cancel() {
      hasCanceled_ = true;
    },
  };
};

const FileImporter = props => {
  const {userGalleryFiles} = props;

  useEffect(() => {
    props.navigation.addListener('beforeRemove', e => {
      e.preventDefault();

      Alert.alert(
        'Cancel?',
        'Are you sure want to cancel this?',
        [
          {text: 'No', onPress: () => {}},
          {
            text: 'Yes!',
            onPress: () => props.navigation.dispatch(e.data.action),
          },
        ],
        {cancelable: true},
      );
    });

    (async () => {
      const selectedFiles = userGalleryFiles.filter(
        file => file.isSelected === true,
      );

      try {
        await makeCancelable(utils.importFiles({files: selectedFiles})).promise;
        console.warn('Oh God!!!');
      } catch (error) {
        console.error(error);
      }

      return () => makeCancelable().cancel();
    })();
  }, []);

  return (
    <Contrainer>
      <TopNavigation title='Importing files...' disableIconLeft />

      <View style = {{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
        <Text hint>0 / 20</Text>
      </View>
    </Contrainer>
  );
};

const mapStateToProps = ({userGalleryFiles}) => ({userGalleryFiles});

export default connect(mapStateToProps)(FileImporter);

Ожидаемые результаты

importFiles.js можно отменить, когда FileImporter.js отключен

Фактические результаты

importFiles.js все еще работает, даже если FileImporter.js размонтирован

Просто проверяйте эту переменную has_canceled перед каждым асинхронным (awaited) шагом.

Bergi 14.12.2020 23:02

@Bergi Спасибо за комментарий, но я все еще не понял, что вы говорите, проверьте переменную hasCanceled_

Firmansyah 15.12.2020 02:53
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
3
498
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

С помощью пользовательского обещания (c-promise ) вы можете сделать следующее ( Смотрите живую демонстрацию):

import { CPromise, CanceledError } from "c-promise2";

const delay = (ms, v) => new Promise((resolve) => setTimeout(resolve, ms, v));

const importFile = async (file) => {
  return delay(1000, file); // simulate reading task
};

function importFiles(files) {
  return CPromise.from(function* () {
    for (let i = 0; i < files.length; i++) {
      try {
        yield importFile(files[i]);
      } catch (err) {// optionally
        CanceledError.rethrow(err);
        console.info(`internal error`, err);
        // handle file reading errors here if you need
        // for example if you want to skip the unreadable file
        // otherwise don't use try-catch block here
      }
    }
  }).innerWeight(files.length);
}

const promise = importFiles([
  "file1.txt",
  "file2.txt",
  "file3.txt",
  "file4.txt"
])
  .progress((value) => {
    console.info(`Progress [${(value * 100).toFixed(1)}%]`);
    // update your progress bar value
  })
  .then(
    (files) => console.info(`Files: `, files),
    (err) => console.warn(`Fail: ${err}`)
  );

setTimeout(() => promise.cancel(), 3500); // cancel the import sequence

Спасибо за ответ, но я не хочу использовать какие-либо пакеты/депозиты.

Firmansyah 15.12.2020 02:47
Ответ принят как подходящий

Попробуйте React useEffect({}, [i]) с deps вместо Recursive Functions

import React, {useEffect, useState} from 'react';
import {View, Alert} from 'react-native';
import {Contrainer, TopNavigation, Text} from '../components/Helper';
import {connect} from 'react-redux';
import utils from '../utils';

const FileImporter = props => {
  const {userGalleryFiles} = props;
  const [currentIndexWantToImport, setCurrentIndexWantToImport] = useState(0)

  useEffect(() => {
    (async () => {
      const selectedFiles = userGalleryFiles.filter(
        file => file.isSelected === true,
      );

      try {
        await utils.importFiles(selectedFiles[currentIndexWantToImport]);
        setCurrentIndexWantToImport(currentIndexWantToImport++);
        console.warn('Oh God!!!');
      } catch (error) {
        console.error(error);
      }
    })();
  }, [currentIndexWantToImport]);

  return (
    <Contrainer>
      <TopNavigation title='Importing files...' disableIconLeft />

      <View style = {{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
        <Text hint>0 / 20</Text>
      </View>
    </Contrainer>
  );
};

const mapStateToProps = ({userGalleryFiles}) => ({userGalleryFiles});

export default connect(mapStateToProps)(FileImporter);

Теперь у вас есть The pure of Recursive Functions от React :)

Вау, это имеет смысл для меня, я попробую: D

Firmansyah 15.12.2020 05:11

Другие вопросы по теме