ArianaLeem
ArianaLeem

Reputation: 1

Async/await and promise all not working as expected

I'm pretty new to javascript and I'm having trouble with async/await and promises. I'm trying to upload an array of images to firebase asynchronously so I get the URLs back for the next step. The following is the upload function.

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);
            });
        });

This is the console every time uploadArray() is called.

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)

But I want the console to log the statements in this order instead.

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!

Upvotes: 0

Views: 353

Answers (2)

Aaron
Aaron

Reputation: 154

Couple tidbits about JS and async/await:

  1. Technically, functions should only be marked as async if it uses await inside it - otherwise you might get undefined behavior when errors occur
  2. Since you're not awaiting the fetch, you're console.log("done!"); should go after the line where you resolve/reject. It's not so it gets executed immediately, that's why your seeing them printed right after "starting"
  3. Nowadays, you usually can replace all calls to .then with an await. Avoiding "callback hell" is very important in JS when it comes to code clarity. Sometimes it's not possible to get around using new Promise(), but if it can be avoided, it should be

Like @Evert's answer, removing callbacks and using proper awaits will likely fix your ordering issues.

It would also be a good idea to add a try catch around all the API calls so that you don't get an unhandled promise exception. It can be added to the uploadImage method, or to where you call Promise.all, whatever works best for your use case.

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);
  }
};

Upvotes: 0

Evert
Evert

Reputation: 99523

The way you are handling promises is a bit strange, and has the new Promise constructor anti-pattern. I rewrote the messy parts here, and I suspect this will solve your ordering issue:

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";
}

The snippet above mostly does what you wrote, but all the unnecessary then's removed. It should be a lot clearer. It's very worthwhile fully learning promises and async/await before you proceed. The code you shared feels very trial and error.

Upvotes: 1

Related Questions