#react-native #axios #mobx-state-tree
#react-native #axios #mobx-state-tree
Вопрос:
У меня есть приложение React Native, в котором я хочу загрузить некоторые файлы с помощью Axios.
Я создал хранилище mobx-state-tree для загрузки файлов, и у каждого файла есть свой собственный CancelTokenSource
, который отправляется на сетевой вызов Axios.
Когда выполняется загрузка, я пытаюсь отменить загрузку, а затем уничтожить элемент.
Самый простой способ, как я показываю ниже, — уничтожить элемент в хранилище, а затем получить beforeDestroy()
хук, который отменяет загрузку. Но такой подход заставляет mobx-state-tree показывать ошибку на скриншоте.
Я также пытался вызвать file.cancelTokenSource.cancel()
явно перед уничтожением элемента. Та же ошибка. Я подозреваю, что операция не полностью отменяется при cancel()
возврате, но поскольку это не асинхронная функция, я не могу await
ее завершить.
Когда я просто вызываю cancel()
без уничтожения, он отменяется просто отлично, поэтому я почти уверен, что это проблема времени, когда destroy(file)
вызывается слишком рано, прежде cancel()
чем убрался после себя.
Что здесь делать?
file-upload-store.ts
import { destroy, flow, Instance, types } from 'mobx-state-tree'
import { FileUpload, IFileUpload } from '../entities/file-upload/file-upload'
import { getApi } from '../../store-environment'
/**
* Store for handling the FileUpload
*/
export const FileUploadStore = types
.model('FileUploadStore')
.props({
files: types.array(FileUpload),
})
.actions((self) => {
const api = getApi(self)
const add = (uri: string, name: string, type: string, size: number) => {
const file = FileUpload.create({
uri,
name,
type,
size,
})
self.files.push(file)
upload(file)
}
const remove = (file: IFileUpload) => {
destroy(file)
}
const cancel = (file: IFileUpload) => {
// also tried this - with no luck
// file.cancelTokenSource.cancel()
destroy(file)
}
const upload = flow(function* (file: IFileUpload) {
file.status = 'pending'
file.uploadedBytes = 0
const { uri, name, type } = file
try {
const id = yield api.uploadFile(uri, name, type, file.setProgress, file.cancelTokenSource.token)
file.status = 'completed'
file.fileUploadId = id
} catch (error) {
file.status = 'failed'
file.error = error.message
}
})
return {
afterCreate() {
// Avoid persistance
self.files.clear()
},
remove,
cancel,
retry: upload,
add,
}
})
export type IFileUploadStore = Instance<typeof FileUploadStore>
file-upload.ts
import { Instance, SnapshotIn, types } from 'mobx-state-tree'
import { CancelToken } from 'apisauce'
/**
* FileUpload contains the particular data of a file, and some flags describing its status.
*/
export const FileUpload = types
.model('FileUpload')
.props({
name: types.string,
type: types.string,
uri: types.string,
size: types.number,
// set if an arror occours
error: types.maybe(types.string),
status: types.optional(types.enumeration(['pending', 'completed', 'failed']), 'pending'),
// updated by progressCallback
uploadedBytes: types.optional(types.number, 0),
// assigned when response from backend is received
fileUploadId: types.maybe(types.string),
})
.volatile(() => ({
cancelTokenSource: CancelToken.source(),
}))
.actions((self) => ({
setProgress(event: ProgressEvent) {
self.uploadedBytes = event.loaded
},
beforeDestroy() {
self.cancelTokenSource?.cancel()
},
}))
export interface IFileUpload extends Instance<typeof FileUpload> {}
// SnapshotIn, used for creating input to store: {Model}.create({})
export interface IFileUploadSnapshotIn extends SnapshotIn<typeof FileUpload> {}
Ответ №1:
Вы уничтожаете FileUpload
узел и отменяете axios
запрос красиво, но отмена запроса выдаст ошибку, поэтому вам нужно убедиться, что ваш FileUpload
узел все еще жив, прежде чем пытаться обновить его в catch
.
import { destroy, flow, Instance, types, isAlive } from 'mobx-state-tree'
// ...
const upload = flow(function* (file: IFileUpload) {
const { uri, name, type } = file
file.status = "pending"
file.uploadedBytes = 0
try {
const id = yield api.uploadFile(
uri,
name,
type,
file.setProgress,
file.cancelTokenSource.token
)
file.status = "completed"
file.fileUploadId = id
} catch (error) {
if (isAlive(file)) {
file.status = "failed"
file.error = error.message
}
}
})
Комментарии:
1. Спасибо, это работает! Я не думал об исключении, создаваемом axios при отмене, как о чем-то, что может заставить его попытаться установить статус failed.