Все Async / await и promise работают не так, как ожидалось

#javascript #reactjs #react-redux #firebase-storage

# #javascript #reactjs #реагировать-redux #firebase-хранилище

Вопрос:

Я довольно новичок в javascript, и у меня возникли проблемы с async / await и promises. Я пытаюсь асинхронно загрузить массив изображений в firebase, чтобы вернуть URL-адреса для следующего шага. Ниже приведена функция загрузки.

 const uploadImage = async (file) => {
            console.log("starting...");
            const storageRef = firebase.storage().ref("forum/"   file[1]);
            return new Promise((resolve, reject) => {
                fetch(file[0])
                    .then(async (res) => {
                        console.log("res to blob");
                        return await res.blob();
                    })
                    .then(async (blob) => {
                        blob.name = file[1];
                        console.log("Starting to put file...", blob);
                        await storageRef.put(blob).then(async () => {
                            const url = await storageRef.getDownloadURL();
                            urlArray.push(url);
                            console.log(url);
                            !url ? resolve("got url!") : reject("it broke");
                        });
                    });
                console.log("done!");
            });
        };

        const uploadArray = async (imageArray) => {
            return await Promise.all(
                imageArray.map((image) => uploadImage(image))
            );
        };

        uploadArray(project.images).then((urls) => {
            urls.forEach((element) => {
                console.log(element);
            });
        });
 

Это консоль при каждом вызове uploadArray() .

 starting...
done!
starting...
done!
res to blob
Starting to put file... Blob {name: "IMG_3851.JPG", size: 1417851, type: "image/jpeg"}
Starting to put file... Blob {name: "IMG_3852.JPG", size: 2391056, type: "image/png"}
https://firebasestorage.googleapis.com/... (Link to firebase)
Uncaught (in promise) it broke```
https://firebasestorage.googleapis.com/... (Link to firebase)
 

Но я хочу, чтобы консоль регистрировала операторы в этом порядке.

 starting
res to blob
Starting to put file... Blob {name: "IMG_3851.JPG", size: 1417851, type: "image/jpeg"}
https://firebasestorage.googleapis.com/... (Link to firebase)
done!
starting
res to blob
Starting to put file... Blob {name: "IMG_3852.JPG", size: 2391056, type: "image/png"}
https://firebasestorage.googleapis.com/... (Link to firebase)
done!
 

Ответ №1:

Способ обработки обещаний немного странный и имеет new Promise анти-шаблон конструктора. Я переписал здесь беспорядочные части, и я подозреваю, что это решит вашу проблему с заказом:

 const uploadImage = async (file) => {
   console.log("starting...");
   const storageRef = firebase.storage().ref("forum/"   file[1]);
   const res = await fetch(file[0]);
   const blob = await res.blob();
   blob.name = file[1];
   console.log("Starting to put file...", blob);
   await storageRef.put(blob);

   const url = await storageRef.getDownloadURL();
   urlArray.push(url);
   console.log(url);
   if (url) {
      return "got url";
   }
   throw "it broke";
}
 

Приведенный выше фрагмент в основном выполняет то, что вы написали, но все ненужное затем удаляется. Это должно быть намного понятнее. Очень полезно полностью изучить promises и async / await, прежде чем продолжить. Код, которым вы поделились, выглядит как метод проб и ошибок.

Ответ №2:

Пара лакомых кусочков о JS и async / await:

  1. Технически функции должны быть помечены как асинхронные, только если они используются await внутри него — в противном случае вы можете получить неопределенное поведение при возникновении ошибок
  2. Поскольку вы не ожидаете fetch , вам console.log("done!"); следует перейти к строке, в которой вы разрешаете / отклоняете. Это не значит, что оно выполняется немедленно, поэтому вы видите их напечатанными сразу после "starting"
  3. В настоящее время вы обычно можете заменить все вызовы на .then на await . Избегать «ада обратного вызова» очень важно в JS, когда дело доходит до ясности кода. Иногда невозможно обойти использование new Promise() , но если этого можно избежать, это должно быть

Как и в ответе @Evert, удаление обратных вызовов и использование правильных ожиданий, скорее всего, устранит ваши проблемы с заказом.

Также было бы неплохо добавить try catch для всех вызовов API, чтобы вы не получили необработанное исключение promise. Его можно добавить в uploadImage метод или туда, куда вы вызываете Promise.all , в зависимости от того, что лучше всего подходит для вашего варианта использования.

 const uploadImage = async (file) => {
  try {
    console.log("starting...");
    const storageRef = firebase.storage().ref("forum/"   file[1]);
    const res = await fetch(file[0]);
  
    console.log("res to blob");
    const blob = await res.blob();
    blob.name = file[1];
  
    console.log("Starting to put file...", blob);
    await storageRef.put(blob);
    const url = await storageRef.getDownloadURL();
    urlArray.push(url);
  
    console.log(url);
    if (!url) throw new Error("it broke");

    console.log("done!");
  } catch (err) {
    console.error(err);
  }
};
 

Комментарии:

1. Я раньше не слышал о вашем первом пункте, но теперь мне стало любопытно — не могли бы вы немного рассказать об этом? Почему ошибка в асинхронной функции без await вызывает неопределенное поведение?

2. Я думаю, что делать общую попытку ..catch в этом случае плохая идея. Идея исключений заключается в том, что они должны всплывать до такой степени, что вы можете сделать с ними что-то полезное. Вы регистрируете ошибку (полезно), но все, что вызывает uploadImage, по-прежнему считает, что оно было успешным, потому что оно в основном получило успешный ответ.