卸载组件时,如何取消 React 中的重复承诺(递归函数)?
How do I cancel a repeated promise (Recursive Functions) in React when the component is unmounted?
大家好
我正在使用 React Native
,我想添加一些功能,用户可以将多个文件导入我的应用程序,用户可以随时取消导入进度
但是,当用户导入这些文件时,我想一个一个导入,告诉用户哪些文件已经导入成功,哪些文件没有导入,
这对我很重要,因为我想告诉用户有多少已选择的文件已成功导入,并且对于在文件导入过程中向 UI 显示每个文件也很有用,这需要我用 Recursive Functions
,
问题是 我不知道如何取消使用 Recursive Functions
的 Promise
,I try with makeCancelable
method from react site 它不起作用而且我认为它只是取消了树 Recursive Functions
顶部的 Promise
,而不是所有执行的 Promise。另外,如果可能的话,我不想使用任何 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
已卸载
自定义承诺 (c-promise) you can do the following (See the live demo):
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.log(`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.log(`Progress [${(value * 100).toFixed(1)}%]`);
// update your progress bar value
})
.then(
(files) => console.log(`Files: `, files),
(err) => console.warn(`Fail: ${err}`)
);
setTimeout(() => promise.cancel(), 3500); // cancel the import sequence
尝试使用 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);
现在你有 来自 React 的 Recursive Functions
的纯粹 :)
大家好
我正在使用 React Native
,我想添加一些功能,用户可以将多个文件导入我的应用程序,用户可以随时取消导入进度
但是,当用户导入这些文件时,我想一个一个导入,告诉用户哪些文件已经导入成功,哪些文件没有导入,
这对我很重要,因为我想告诉用户有多少已选择的文件已成功导入,并且对于在文件导入过程中向 UI 显示每个文件也很有用,这需要我用 Recursive Functions
,
问题是 我不知道如何取消使用 Recursive Functions
的 Promise
,I try with makeCancelable
method from react site 它不起作用而且我认为它只是取消了树 Recursive Functions
顶部的 Promise
,而不是所有执行的 Promise。另外,如果可能的话,我不想使用任何 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
已卸载
自定义承诺 (c-promise) you can do the following (See the live demo):
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.log(`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.log(`Progress [${(value * 100).toFixed(1)}%]`);
// update your progress bar value
})
.then(
(files) => console.log(`Files: `, files),
(err) => console.warn(`Fail: ${err}`)
);
setTimeout(() => promise.cancel(), 3500); // cancel the import sequence
尝试使用 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);
现在你有 来自 React 的 Recursive Functions
的纯粹 :)