Async/Await not working as expected with Promise.all and a .map function
Asked Answered
P

2

8

I have a slew of async functions I'm using and I'm having a weird issue.

My code, working, looks like:

async mainAsyncFunc (metadata) {
  let files = metadata.map(data => this.anotherAsyncFunc(data.url));
  return Promise.all(files);
}

anotherAsyncFunc function looks like:

  async anotherAsyncFunc (url) {
    return await axios({
      url,
    }).then(res => res.data)
      .catch(err => {
      throw err;
    });
  }

My issue comes when I try to append more data to what the first function (mainAsyncFunc) returns. My thoughts are to do that in the map, naturally, and when all is said and done, modified it looks like:

async mainAsyncFunc (metadata) {
    files = metadata.map(data => {
        return new Promise((resolve) => {
          let file = this.anotherAsyncFunc(data.download_url);
          let fileName = data.name;
          resolve({
            file,
            fileName
          });
        });
      });
    return Promise.all(files);
}

If it's not clear, I'm getting the file itself like normal, and appending a fileName to it, then resolving that object back.

For some reason, this is returning a pending Promise, whereas I would expect it to wait for them to be fulfilled and then returned as a complete file and name in an object. Any help understanding what I'm doing wrong would be greatly appreciated.

Paraffinic answered 3/4, 2017 at 20:51 Comment(6)
async functions return promises. Nor can you turn a Promise-wrapped value into an ordinary value, you will always have to await or call .then. Why did you expect to get an array?Flotow
@JaredSmith I use Promise.all() because I'm mapping over a dataset and returning the result of async function for each item. Thus creating an array. However, I just solved my own issue and am posting a solution nowParaffinic
Promise.all returns a Promise. Again, you cannot 'unwrap' a promise. You can only .then it.Flotow
@JaredSmith I can assure you my working code is returning an array of fulfilled objects :). If it helps you understand it better, I call this function like: FileRetriever.mainAsyncFunc(metadata).then(contents => ...), and with my new working code contents is an array.Paraffinic
No, its not, your code does not work the way you seem to think it does. await is sugar over .then. You cannot ever unwrap a promise. Its a logical impossibility in the general case, as there is no way to tell if a promise is resolved or not.Flotow
Handy tip: Any time you find yourself using new Promise when the values you're working with are already promises, it usually means you've got something wrong.Foreordain
F
13

It looks like you've solved your issue, just as a bit of a pointer, you can further simplify your code as follows:

async anotherAsyncFunc (url) {
    return (await axios({ url })).data;
}

async mainAsyncFunc (metadata) {
    let files = metadata.map(async data => ({
        file: await this.anotherAsyncFunc(data.download_url),
        fileName: data.name
    }));

    return Promise.all(files);
}
Foreordain answered 3/4, 2017 at 21:15 Comment(0)
P
4

Just solved the issue:

  files = metadata.map(async (data) => {
    let file = await this.anotherAsyncFunction(data.download_url);
    let fileName = data.name;
    return {
      file,
      fileName
    };
  });

I needed to wrap the anonymous function in async so I could use await inside of it. Hopefully this helps anyone else with a similar problem!

Paraffinic answered 3/4, 2017 at 20:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.