Отмена запроса на загрузку перед уничтожением объекта приводит к тому, что выбрасывание mobx-state-tree не может изменить [мертвые] ошибки

#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.