Добрый день, ребята, вот проблема, которая в настоящее время ведет меня к стене, и я не могу видеть дерево для деревьев.
У меня есть приложение React Native со встроенным средством загрузки файлов, часть этот загрузчик - это использование abortcontroller, который позволяет коду отправить сигнал в запрос на выборку, чтобы остановить вызов в полете. Это работает отлично, так как можно было бы ожидать, что проблема заключается в том, что пользователь затем выбирает другой файл или пытается загрузить файл ранее отмененное мое обещание возвращается немедленно с ошибкой отмены, которая все еще остается на месте, предотвращая дальнейшие загрузки, и ради любви и денег я не могу найти способ предотвратить это.
Вот уменьшенная версия моего экрана (URL-адреса удалены, некоторые точки данных удалены, и т. д. c), чтобы защитить конфиденциальность моей системы
import React from 'react';
import {StyleSheet,View,ScrollView,Alert,} from 'react-native';
import AppSettings from '../../constants/AppSettings'
import Colours from '../../constants/Colours';
import CustomHeader from '../../components/CustomHeader';
import CustomButton from '../../components/CustomButton';
import TextContent from '../../components/TextContent';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import Constants from 'expo-constants';
import * as DocumentPicker from 'expo-document-picker';
import * as Permissions from 'expo-permissions';
import CustomInput from '../../components/CustomInput';
import Progressbar from '../../components/ProgressBar';
import * as Network from 'expo-network';
export default class UploadScreen extends React.Component {
state = {
file: null,
filetype: null,
fileExtention:null,
uploading: false,
pickResult: null,
mainDataLoaded: false,
docDesc:null,
enteredPassword:'',
currentUploadPercent:0,
uploadTime: 0,
startPick: false,
};
render() {
return (
<View style={styles.screenContainer}>
<LinearGradient start={[0, 1]} end={[0,0.9]} colors={['rgba(163, 163, 163,1)', 'rgba(163, 163, 163,0)']} style={{flex:1}} >
<ScrollView style={styles.scrollContainer} contentContainerStyle={styles.defaultContainer}>
<View style={{flex:1, alignItems: 'center', justifyContent: 'center', width:'100%' }}>
<TextContent>Upload file containing: {this.state.docDesc}</TextContent>
{this._maybeRenderFile()}
{this._maybeRenderControls()}
{this._maybeRenderUploadingIndicator()}
</View>
</ScrollView>
</LinearGradient>
</View>
);
}
_maybeRenderUploadingIndicator = () => {
if (this.state.uploading) {
return (
<View style={{width:'80%',alignItems:'center'}}>
<Progressbar progress={this.state.currentUploadPercent}/>
<CustomButton style={{width:'100%'}} onPress={()=>{AbortUpload(this)}} title='Cancel Upload'></CustomButton>
</View>
);
}
};
_maybeRenderControls = () => {
if (!this.state.uploading) {
return (
<View style={{width:'100%',alignItems:'center'}}>
<CustomButton style={{width:'80%'}} onPress={this._pickImage} title='Select file to upload'><MaterialCommunityIcons style={{color:Colours.PrimaryButtonText}} name="folder-open" size={30}/></CustomButton>
</View>
);
}
};
_maybeRenderFile = () => {
if (this.state.file) {
switch (this.state.filetype) {
case 'application/pdf':
const passwordHandler = enteredText => {
this.setState({enteredPassword: enteredText});
};
return (
<View style={{alignItems:'center'}}>
<MaterialCommunityIcons style={{color:Colours.PrimaryText}} name="file-pdf" size={100}/>
<TextContent style={{textAlign:'center'}}>File to upload: {this.state.file}</TextContent>
<TextContent>If this file requires a password to access please type it below or leave blank if not required.</TextContent>
{!this.state.uploading && (
<View>
<CustomInput placeholder='PDF Password (if applicable)' autoCapitalize='characters' autoCompleteType='off' autoCorrect={false} textContentType='none' onChangeText={passwordHandler} value={this.state.enteredPassword}/>
<CustomButton style={{width:'100%'}} onPress={()=>{this._handleImagePicked(this.state.pickResult)}} title='Upload this file'><MaterialCommunityIcons style={{color:Colours.PrimaryButtonText}} name="file-upload-outline" size={30}/></CustomButton>
<TextContent style={{textAlign:'center'}}>Or</TextContent>
</View>
)}
</View>
);
break;
case 'image/jpg':
case 'image/png':
case 'image/gif':
return (
<View style={{alignItems:'center'}}>
<MaterialCommunityIcons style={{color:Colours.PrimaryText}} name="file-image" size={100}/>
<TextContent style={{textAlign:'center'}}>File to upload: {this.state.file}</TextContent>
{!this.state.uploading && (
<View>
<CustomButton style={{minWidth:'80%'}} onPress={()=>{this._handleImagePicked(this.state.pickResult)}} title='Upload this file'><MaterialCommunityIcons style={{color:Colours.PrimaryButtonText}} name="file-upload-outline" size={30}/></CustomButton>
<TextContent style={{textAlign:'center'}}>Or</TextContent>
</View>
)}
</View>
);
break;
default:
break;
}
}
};
_askPermission = async (type, failureMessage) => {
const { status, permissions } = await Permissions.askAsync(type);
if (status === 'denied') {
alert(failureMessage);
}
};
_pickImage = async () => {
await this._askPermission(
Permissions.CAMERA_ROLL,
'We need the file permission to access files from your phone...'
);
if(!this.state.startPick){
this.setState({startPick: true})
let pickerResult = await DocumentPicker.getDocumentAsync({});
if(pickerResult.type == 'success'){
this.setState({startPick: false})
//Get file extention
var splitAt = pickerResult.name.lastIndexOf(".")
var fileExt = pickerResult.name.slice(splitAt,pickerResult.name.length).toLowerCase()
switch (fileExt) {
case '.pdf':
this.setState({file: pickerResult.name, filetype: 'application/pdf', pickResult: pickerResult, fileExtention: fileExt})
break;
case '.jpg':
this.setState({file: pickerResult.name, filetype: 'image/jpg', pickResult: pickerResult, fileExtention: fileExt})
break;
case '.jpeg':
this.setState({file: pickerResult.name, filetype: 'image/jpg', pickResult: pickerResult, fileExtention: fileExt})
break;
case '.png':
this.setState({file: pickerResult.name, filetype: 'image/png', pickResult: pickerResult, fileExtention: fileExt})
break;
case '.gif':
this.setState({file: pickerResult.name, filetype: 'image/gif', pickResult: pickerResult, fileExtention: fileExt})
break;
default:
this.setState({file: null, filetype: null, pickResult: null})
Alert.alert('Unsupported filetype','For security reasons you may only select images or PDF files to upload.')
break;
}
}else{
//No file selected
this.setState({file: null, filetype: null, pickResult: null})
this.setState({startPick: false})
}
if(__DEV__){console.log('Result:', pickerResult)}
}else{
if(__DEV__){console.log('Pick already started')}
}
};
_StatusCheck = async() =>{
return fetch('Url for server side upload status response')
.then((response) => response.json())
.then((responseJson) => {
return responseJson
})
.catch((error) =>{
console.error(error);
});
}
_handleImagePicked = async pickerResult => {
try {
if (!pickerResult.cancelled) {
var thisTime = Date.now()
this.setState({uploadTime: thisTime})
var myPromise = new Promise(function(){})
myPromise = MakeQuerablePromise(new uploadFileAsync(
pickerResult.uri,
this.state.docType + '-' + Date.now() + this.state.fileExtention,
this.state.filetype,
this.state.docType,
this.state.docDesc,
this.state.enteredPassword,
this.state.quoteID,
this.state.uploading,
thisTime,
controller.signal
));
this.setState({ uploading: true });
if(__DEV__){
console.log("Initial fulfilled:", myPromise.isFulfilled());//false
console.log("Initial rejected:", myPromise.isRejected());//false
console.log("Initial pending:", myPromise.isPending());//true
}
for (let index = 0; index < 1;) {
var currentStatus = await this._StatusCheck()
var curTime = new Date()
if(__DEV__){
console.log('Time:',curTime.getHours(),':',curTime.getMinutes(),':',curTime.getSeconds())
console.log('Status:',currentStatus)
console.log("Promise status- fulfilled:", myPromise.isFulfilled(), "rejected:", myPromise.isRejected(),"pending:", myPromise.isPending());//false
console.log('Promise Content:',myPromise)
}
if(!myPromise.isRejected()){
if(currentStatus.percent != undefined){
if(currentStatus.percent < 100){
this.setState({currentUploadPercent:currentStatus.percent})
if(__DEV__){
console.log('Upload progess ' + currentStatus.percent)
console.log("Promise status: fulfilled:", myPromise.isFulfilled(), "rejected:", myPromise.isRejected(),"pending:", myPromise.isPending());//false
}
}else{
this.setState({currentUploadPercent:currentStatus.percent})
if(__DEV__){
console.log('Upload progess 100%')
console.log("Promise status: fulfilled:", myPromise.isFulfilled(), "rejected:", myPromise.isRejected(),"pending:", myPromise.isPending());//false
}
}
}
}
if(myPromise.isFulfilled() == true){
if(__DEV__){
console.log("Entered Fulfilled State - Promise status: fulfilled:", myPromise.isFulfilled(), "rejected:", myPromise.isRejected(),"pending:", myPromise.isPending());//false
}
index++
}
if(myPromise.isRejected() == true){
if(__DEV__){
console.log("Entered Rejected State - Promise status: fulfilled:", myPromise.isFulfilled(), "rejected:", myPromise.isRejected(),"pending:", myPromise.isPending());//false
}
index++
}
}
if(myPromise.isRejected() == false){
myPromise.then(response => response.json()).then((responseJson)=>{
if(__DEV__){
console.log('Promise Json:',responseJson)
console.log("Final fulfilled:", myPromise.isFulfilled());//true
console.log("Final rejected:", myPromise.isRejected());//false
console.log("Final pending:", myPromise.isPending());//false
}
if(responseJson.datapage.result.gitUploadStatus.successful == true){
//Successful upload
this.props.navigation.navigate('CaptureThanks')
}else{
//Upload had a issue
Alert.alert('Upload error','There was an issue with the upload, this maybe a temporary issue with your connection or with the server please ensure you have a good steady signal and try again, if the problem persists please contact us. Error Code: '+responseJson.datapage.gitUploadStatus.errorMessage)
}
})
}else{
//Rejected promise handle failure
if(myPromise.rejectReason() == 'AbortError'){
myPromise = MakeQuerablePromise(new Promise(function(resolve, reject){
resolve('AbortError')
}))
}else{
Alert.alert('Upload error','There was an issue with the upload, this maybe a temporary issue with your connection or with the server please ensure you have a good steady signal and try again, if the problem persists please contact us. Error Code: '+responseJson.datapage.gitUploadStatus.errorMessage)
}
}
}
} catch (e) {
if(__DEV__){
console.log('Error Name:',e.name)
console.log('Catch Error:',{ e });
}
return;
} finally {
if(__DEV__){
console.log('Reached Final')
}
myPromise = MakeQuerablePromise(new Promise(function(resolve, reject){
resolve('Finished')
}))
console.log(myPromise)
this.setState({ uploading: false, currentUploadPercent:0 });
}
};
}
function MakeQuerablePromise(promise) {
// Don't modify any promise that has been already modified.
if (promise.isResolved){
return promise
};
// Set initial state
var isPending = true;
var isRejected = false;
var isFulfilled = false;
var rejectReason = '';
// Observe the promise, saving the fulfillment in a closure scope.
var result = promise.then(
function(v) {
isFulfilled = true;
isPending = false;
rejectReason = '';
return v;
},
function(e) {
isRejected = true;
isPending = false;
rejectReason = e.name;
return e;
}
);
result.isFulfilled = function() { return isFulfilled; };
result.isPending = function() { return isPending; };
result.isRejected = function() { return isRejected; };
result.rejectReason = function() {return rejectReason; };
return result;
}
const controller = new AbortController;
async function uploadFileAsync(uri,name,type,docType,docDesc,password,quoteid,isUploading,uploadTime,abortSignal) {
if(!isUploading){
if(__DEV__){console.log('Making upload request for ',docType,' Document description:', docDesc)}
let apiUrl = 'Url to push the upload to';
let formData = new FormData();
//(method) FormData.append(name: string, value: string | Blob, fileName?: string): void
formData.append('filename', {
uri,
name: name,
type: type,
documentType:docType,
description:docDesc,
password:password,
quoteid:quoteid,
});
let options = {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
},
signal: abortSignal,
};
if(__DEV__){console.log('Options:', options)}
return fetch(apiUrl, options);
}else{
return null
}
}
async function AbortUpload(stateObject){
controller.abort()
stateObject.setState({isUploading: false})
}
UploadScreen.navigationOptions = {
header: () => <CustomHeader goback={true} title='Document Upload'/>,
title: AppSettings.AppName + ' ',
headerTitleStyle:{
fontFamily: AppSettings.HeaderFont,
},
headerStyle: {
backgroundColor: Colours.HeaderBackground
},
headerTintColor: Colours.HeaderText
};
const styles = StyleSheet.create({
screenContainer:{
flex:1,
backgroundColor: Colours.PrimaryBackgroud,
},
scrollContainer:{
flex: 1,
height:'100%'
},
defaultContainer: {
alignItems: 'center',
},
});
Извините за характер моего кода, так как я все еще довольно нов, чтобы реагировать на нативный и только делал это в течение нескольких месяцев, так что до сих пор в моей голове много функций и сист и только недавно обновил до последней версии expo (36) вчера, чтобы я мог включить прерывание извлечения.
Но у кого-то есть какие-либо подсказки относительно того, почему кажется, что после того, как сигнал вызывается один раз, чтобы прервать каждый тогда в будущем запросе, похоже, будет получен тот же сигнал, несмотря на то, что пользователь не щелкнул его снова, и я не храню его в состоянии, поэтому я не могу понять, почему он сохраняется до такой степени, что я даже дошел до крайности восстановления обещания в начале и в конце, чтобы гарантировать, что они были очищены при каждой загрузке.