Home > Software design >  How to write an asynchronous function
How to write an asynchronous function

Time:11-06

I'm doing my first ever react website and I need help to write an asynchronous JavaScript function. Here I'm uploading the user input files to a firebase storage and then making a post request to the API to store the data on the database. However, since the firebase upload takes some time to upload the data to its storage, the API request happens before the upload finishes, therefore the data does not get uploaded to the db. Now I know I should use promises of async await keywords here, but I can't figure out how to. I'd appreciate if someone could help. Thanks in advance!

Here's the relevant code snippet:

const save = (items) => {
  items.forEach((item) => {
    const fileName = new Date().getTime()   item.label   item.file.name;
    const uploadTask = storage.ref(`/items/${fileName}`).put(item.file);
    uploadTask.on(
      "state_changed",
      (snapshot) => {
        const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        console.log("Upload is"   progress   "% done.");
      },
      (err) => {
        console.log(err)
      },
      () => {
        storage.ref("items").child(fileName).getDownloadURL().then((url) => {
          setSong((prev) => {
            return { ...prev, [item.label]: url };
          });
        });
      }
    );
  })
  console.log(song)
  axios.post("songs/create", song);
}

PS: Here, items is the array of input files from the user, each file is with a label and it is how the attributes are named on the json document. setSong is a useState function. Here The JSON document already contains the other user inputs(which are not files), and the setSong method is used to append the firebase URLs of the files to it.

CodePudding user response:

You have to wait for all files to get uploaded then you can call your API, in order to do that you should use Promise.all to wait to resolve all files :

  const save = items => {
  Promise.all(
    items.map(item => {
      return new Promise(resolve => {
        const fileName = new Date().getTime()   item.label   item.file.name
        const uploadTask = storage.ref(`/items/${fileName}`).put(item.file)
        uploadTask.on(
          'state_changed',
          snapshot => {
            const progress =
              (snapshot.bytesTransferred / snapshot.totalBytes) * 100
            console.log('Upload is'   progress   '% done.')
          },
          err => {
            console.log(err)
          },
          () => {
            storage
              .ref('items')
              .child(fileName)
              .getDownloadURL()
              .then(url => {
                setSong(prev => {
                  return { ...prev, [item.label]: url }
                })
                resolve({[item.label]: url})
              })
          }
        )
      })
    })
  ).then((res) => {
    const song = {}
    res.forEach(item => {
      return {
        ...song,
        ...item
      }
    })
    axios.post('songs/create', song)
  })
}

CodePudding user response:

Explanation

Functions and Async

Async/Await can be implemented wherever a function starts. Functions can be written in following forms:

function name(){};
function name() => {};

To write an async function, you would do the following:

async function name(){};

All of these functions are called functions though, to make functions run without calling them, we need to turn them into IIFE's, or Immediately Invoked Function Execution. If you want to create a function and execute it immediately you would surround the function in ()'s and end it with an ();.

(function () {})();

If we simplify this:

(() => {})();

Implementing async would go like this:

(async () => {})();

Await

The await operator is used to wait for a Promise, puting await in front of an expression that uses promises makes it wait for the promise. If it is used in front of an expression that doesn't use promises, it is redundant and your code editor/IDE will say so.

(async () => {
    const str = await 'some string';
    console.log(str);
})();

await is redundant here since the expression 'some string' does not relate to a promise, but a string.

(async () => {
  const myPromise = new Promise((resolve, reject) =>
    resolve('some string')
  );

  const str = await myPromise.then(x => console.log(x));
})();

await is properly used here since the expression myPromise is related to a promise that outputs a string.

Implementation

I'm not 100% sure how the api works within promises, I recommend you figure it out yourself, but this is my educated guess:

const save = (items) => {
  Promise.all(
    items.map((item) => {
      return new Promise(async (resolve) => {
        const fileName = new Date().getTime()   item.label   item.file.name;
        const uploadTask = await storage
          .ref(`/items/${fileName}`)
          .put(item.file);
        await uploadTask.on(
          "state_changed",
          (snapshot) => {
            const progress =
              (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
            console.log("Upload is"   progress   "% done.");
          },
          (err) => {
            console.log(err);
          },
          async () => {
            await storage
              .ref("items")
              .child(fileName)
              .getDownloadURL()
              .then((url) => {
                setSong((prev) => {
                  return { ...prev, [item.label]: url };
                });
                resolve({ [item.label]: url });
              });
          }
        );
      });
    })
  ).then(async (res) => {
    const song = {};
    res.forEach((item) => {
      return {
        ...song,
        ...item,
      };
    });
    await axios.post("songs/create", song);
  });
};
  • Related