Всем привет
Я использую 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
перед каждым асинхронным (await
ed) шагом.
@Bergi Спасибо за комментарий, но я все еще не понял, что вы говорите, проверьте переменную hasCanceled_
С помощью пользовательского обещания (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
Спасибо за ответ, но я не хочу использовать какие-либо пакеты/депозиты.
Попробуйте 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
Начните с никогда не передавайте асинхронную функцию в качестве исполнителя новому промису!