Небольшое приложение для загрузки файлов, при этом я хотел бы получать информацию о ходе выполнения каждого выполненного элемента. Однако setCount не увеличивается каждый раз, когда файл загружается в Firebase, он увеличивается только на единицу каждый раз после завершения всей загрузки.
import { ref, uploadBytesResumable } from "firebase/storage"
import { proStorage } from "../firebase/config"
import { v4 } from "uuid"
import { useState } from "react"
const useUploader = (data, foldername) => {
const [upLoadcomplete, setuploacomplete] = useState("")
const [count, setCount] = useState(0)
const uploadFiles = async () => {
for (let i = 0; i < data.length; i++) {
const name = `$${v4()}`
const location = `${foldername}/${name}`
const metadata = {
customMetadata: {
uuid_name: name,
},
}
const dataRef = ref(proStorage, location)
const uploadtask = uploadBytesResumable(dataRef, data[i], metadata)
uploadtask.on(
"state_changed",
(snapshot) => {
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
console.info("Upload is " + progress + "% done")
// eslint-disable-next-line
switch (snapshot.state) {
case "paused":
console.info("Upload is paused")
break
case "running":
console.info("Upload is running")
setCount( count + 1 )
break
}
setuploacomplete("complete")
},
(error) => {
setuploacomplete("Error with upload try again")
}
)
}
console.info(upLoadcomplete)
}
return [uploadFiles, upLoadcomplete]
}
export default useUploader
Похоже, что setCount должен увеличиваться каждый раз, когда статус меняется на «Выполняется». Но это не так?
Есть идеи
Это вроде как работает, за исключением того, что сначала берется общая сумма для загрузки и добавляются к ней итерации... например, при загрузке 5 файлов сообщается 5, а затем 6,7,8,9,10. Добавление 0,5 работает... но менее идеально.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


В вашем коде вы инициализируете count как 0. Это означает, что этот код:
const [upLoadcomplete, setuploacomplete] = useState("")
const [count, setCount] = useState(0)
const uploadFiles = async () => {
// ...
setCount(count + 0)
// ...
}
фактически становится
const [upLoadcomplete, setuploacomplete] = useState("")
const [count, setCount] = useState(0)
const uploadFiles = async () => {
// ...
setCount(0 + 1) // always sets count to 1
// ...
}
Как описано @evolutionxbox в их комментарии, один из способов обойти эту проблему — вызвать setCount с функцией обратного вызова, которая принимает старое значение и возвращает новое.
const [upLoadcomplete, setuploacomplete] = useState("")
const [count, setCount] = useState(0)
const uploadFiles = async () => {
// ...
setCount(oldCount => oldCount + 1) // always adds 1 to the previous value
// ...
}
Как вы заметили в своем комментарии , пока это работает, вы сталкиваетесь с некоторыми проблемами параллелизма. Когда вы звоните uploadFiles, чтобы начать загрузку, все 5 загрузок запускаются одновременно, и каждая вызывает setCount(oldCount => oldCount + 1). В результате ваш первоначальный счет будет равен 5 (как и ожидалось). Однако для каждого последующего события state_changed, в котором состояние равно running, вы снова увеличиваете этот счетчик (в результате чего count достигает любого числа в зависимости от размера ваших файлов).
Чтобы это исправить, вам нужно звонить setCount только один раз при каждой загрузке.
const useUploader = (data, foldername) => {
const [upLoadcomplete, setuploacomplete] = useState("")
const [count, setCount] = useState(0)
const uploadFiles = async () => {
for (let i = 0; i < data.length; i++) {
// ...
const uploadtask = // ...
let started = false;
uploadtask.on(
"state_changed",
(snapshot) => {
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
console.info("Upload thread #"+ i + " is " + progress + "% done")
// eslint-disable-next-line
switch (snapshot.state) {
case "paused":
console.info("Upload is paused")
break
case "running":
console.info("Upload is running")
if (!started) {
setCount(oldCount => oldCount + 1)
started = true;
}
break
}
// removed: setuploacomplete("complete") (it shouldn't be here)
},
(error) => {
// this upload has encountered an error (but not necessarily all of them)
setuploacomplete("Error with upload try again")
},
() => {
// this upload has completed (but not necessarily all of them)
}
)
}
}
return [uploadFiles, upLoadcomplete];
}
К сожалению, при этом отсчет все равно начнется с 5, а затем уже никогда не изменится. Он также может никогда не установить счетчик, если загрузка завершилась до обработки события state_changed. Если вы хотите обновлять счетчик после завершения каждой загрузки, вместо этого вам следует переместить эту логику в обратный вызов onComplete:
// ...
() => {
// this upload has completed (but not necessarily all of them)
setCount(oldCount => oldCount + 1) // increments count, only once the upload finishes
}
// ...
Но теперь у вас есть проблема: count может никогда не достичь длины data, поскольку любая загрузка может завершиться ошибкой. Хотя вы можете добавить ту же самую логику setCount в обработчик ошибок, исправление этих проблем с параллелизмом выходит за рамки этого ответа.
Вместо этого вам лучше создать новый компонент UploadTask, который обрабатывает одну загрузку и соответствующим образом управляет ее промежуточными состояниями. Затем, когда у вас есть массив типа data, вы должны использовать компонент UploadTaskBatch, который порождает один компонент UploadTask для каждой записи в data. Это позволяет вам позволить React выполнять за вас тяжелую работу по обработке одновременных обновлений состояния. Их реализация оставлена в качестве упражнения для читателя.
вау... это очень исчерпывающий ответ, и большое вам спасибо! Я попробую и посмотрю, как пойдет дело! Еще раз большое спасибо за объяснения, я учусь по ходу дела, и очень приятно получить такую помощь.
Поскольку обновление состояния не синхронно, может быть,
countне увеличился к моменту повторного вызова? Попробуйте вместо этого использовать функцию обновления состоянияsetCount(prevCount => prevCount + 1)?