0

The function below calls several asynchronous functions in a for loop. It's parsing different CSV files to build a single JavaScript object. I'd like to return the object after the for loop is done. Its returning the empty object right away while it does the asynchronous tasks. Makes sense, however I have tried various Promise / async /await combinations hopes of running something once the for loop has completed. I am clearly not understanding what is going on. Is there a better pattern to follow for something like this or am I thinking about it incorrectly?

async function createFormConfig(files: string[]): Promise<object>

  return new Promise(resolve => {

    const retConfig: any = {};


    for (const file of files) {


      file.match(matchFilesForFormConfigMap.get('FIELD')) ?
        parseCsv(file).then(parsedData => {
          retConfig.fields = parsedData.data;
        })

        : file.match(matchFilesForFormConfigMap.get('FORM'))
        ? parseCsv(file).then(parsedData => retConfig.formProperties = parsedData.data[0])

        : file.match(matchFilesForFormConfigMap.get('PDF'))
          ? parseCsv(file).then(parsedData => retConfig.jsPdfProperties = parsedData.data[0])

          : file.match(matchFilesForFormConfigMap.get('META'))
            ? parseCsv(file).then(parsedData => {
              retConfig.name = parsedData.data[0].name;
              retConfig.imgType = parsedData.data[0].imgType;
              // console.log(retConfig);  <- THIS CONSOLE WILL OUTPUT RETCONFIG LOOKING LIKE I WANT IT
            })

            : file.match(matchFilesForFormConfigMap.get('PAGES'))
              ? parseCsv(file).then(parsedData => retConfig.pages = parsedData.data)
              : console.log('there is an extra file: ' + file);

    }

    resolve(retConfig);  // <- THIS RETURNS: {}
  });

This is the code I'm using to call the function in hopes of getting my 'retConfig' filled with the CSV data.

getFilesFromDirectory(`${clOptions.directory}/**/*.csv`)
  .then(async (files) => {
    const config = await createFormConfig(files);
    console.log(config);
  })
  .catch(err => console.error(err));

};
2

2 Answers 2

2

First, an async function returns a Promise, so you dont have to return one explicitely.Here is how you can simplify your code:

async function createFormConfig(files: string[]): Promise<object> {

  // return new Promise(resolve => { <-- remove

  const retConfig: any = {};

  // ...

  // The value returned by an async function is the one you get
  // in the callback passed to the function `.then` 
  return retConfig;

  // }); <-- remove
}

Then, your function createFormConfig returns the config before it has finished to compute it. Here is how you can have it computed before returning it:

async function createFormConfig(files: string[]): Promise<object> {

  const retConfig: any = {};

  // Return a Promise for each file that have to be parsed
  const parsingCsv = files.map(async file => {
    if (file.match(matchFilesForFormConfigMap.get('FIELD'))) {
      const { data } = await parseCsv(file);
      retConfig.fields = data;
    } else if (file.match(matchFilesForFormConfigMap.get('FORM'))) {
      const { data } = await parseCsv(file);
      retConfig.formProperties = data[0];
    } else if (file.match(matchFilesForFormConfigMap.get('PDF'))) {
      const { data } = await parseCsv(file);
      retConfig.jsPdfProperties = data[0];
    } else if (file.match(matchFilesForFormConfigMap.get('META'))) {
      const { data } = await parseCsv(file);
      retConfig.name = data[0].name;
      retConfig.imgType = data[0].imgType;
    } else if (file.match(matchFilesForFormConfigMap.get('PAGES'))) {
      const { data } = await parseCsv(file);
      retConfig.pages = data;
    } else {
      console.log('there is an extra file: ' + file);
    }
  });

  // Wait for the Promises to resolve
  await Promise.all(parsingCsv)

  return retConfig;
}
Sign up to request clarification or add additional context in comments.

4 Comments

you need to await Promise.all(parsingCsv); or the function won't wait.
Thanks for the feedback!
thanks for the write up. does the await Promise.all(parsingCsv) mean im not returning a promise anymore?
It means you are waiting for an asynchronous operation to end. The fact you declared the function async means you actually are returning a Promise. It's 2 different things
1

async functions already return promises, you don't need to wrap the code in a new one. Just return a value from the function and the caller will receive a promise that resolves to the returned value.

Also, you have made an async function, but you're not actually using await anywhere. So the for loop runs through the whole loop before any of your promises resolve. This is why none of the data is making it into your object.

It will really simplify your code to only use await and get rid of the then() calls. For example you can do this:

async function createFormConfig(files: string[]): Promise<object> {

  const retConfig: any = {};

  for (const file of files) {

    if (file.match(matchFilesForFormConfigMap.get('FIELD')){
      // no need for the then here
      let parsedData = await parseCsv(file)
      retConfig.field = parsedData.data
    }

   // ...etc

At the end you can just return the value:

return retConfig

4 Comments

the comment above points out that i'm returning the promise with the return retConfig so i need to resolve it after i receive it.
Not sure which comment you're talking about @RobinMasters. In your code your not using return retConfig you're returning the Promise you made and using resolve(retConfig). Maybe I'm misunderstanding your comment?
that is correct @Mark Meyer, with your code a return retConfig is returning a promise, so i need to resolve it after I receive it. Just pointing that out. Thanks for the help.
Correct @RobinMasters, async functions always return promises (even you don't return anything).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.