How do I make an asynchronous function return in .map?

I am trying to get the fetchIDFromPerson function to return and assign the returned value to ID before the rest of the fetchPersons function executes. It is only returning undefined since it's async. What can I do to get it to work? Thanks.

const fetchIDFromPerson = name => {
  axios
    .get(`https://swapi.co/api/people/?search=${name}&format=json`)
    .then(response => extractImgIDFromURL(response.data.results[0].url))
    .catch(error => console.error(error));
};

let body;

const fetchPersons = (query, imgID) => {
  SWAPIGraphQL.post("", { query })
    .then(result => {
      const { allPersons } = result.data.data;
      return (body = allPersons.map(person => {
        const ID = fetchIDFromPerson(person.name); //This returns undefined!
        return {
          ...person,
          imgID: ID //needs to be assigned a value from fetchIDFromPerson
        };
      }));
    })
    .catch(error => console.error(error));
};

fetchPersons(GET_PERSONS_DATA);

Upvotes: 1

Views: 62

Answers (1)

jfriend00
jfriend00

Reputation: 707318

You need to make a number of changes to properly use the promises that your asynchronous code return:

  1. return the axios promise to the caller in fetchIDFromPerson().
  2. rethrow error in .catch() after logging it so the caller sees the error
  3. return the return SWAPIGraphQL.post() promise to the caller in fetchPersons().
  4. Get rid of the body variable. You don't want side effect programming, particular with asynchronous code. Communicate back results through returned promises.
  5. Use Promise.all() on the results of the .map() so you know when all those promises are done.
  6. When calling fetchPersons() use the returned promise to get the resolved value.
  7. Inside the .map() return the promise from fetchIDFromPerson().
  8. Add a .then() to where you call fetchIDFromPerson() where you can return the combined data structure with person and ID in it.
  9. Change response.data.results[0].url to response.results[0].url to match the data structure you're getting back.

Here's the fixed up code:

const fetchIDFromPerson = name => {
  return axios
    .get(`https://swapi.co/api/people/?search=${name}&format=json`)
    .then(response => extractImgIDFromURL(response.results[0].url))
    .catch(error => {
        // log and rethrow
        console.error(error);
        throw error;
    });
};


const fetchPersons = (query, imgID) => {
  return SWAPIGraphQL.post("", { query })
    .then(result => {
      const { allPersons } = result.data.data;
      return Promise.all(allPersons.map(person => {
        return fetchIDFromPerson(person.name).then(ID => {
            return {
              ...person,
               imgID: ID
            };
        });
      }));
    })
    .catch(error => {
        // log error and rethrow
        console.error(error);
        throw error;
    });
};

fetchPersons(GET_PERSONS_DATA).then(results => {
    console.log(results);
}).catch(err => {
    console.log(err);
});

You can simplify fetchPersons() a bit by using async/await and not logging the error as many separate places:

const fetchPersons = async (query, imgID) => {
    const result = await SWAPIGraphQL.post("", { query });
    const { allPersons } = result.data.data;
    return Promise.all(allPersons.map(async (person) => {
        const ID = await fetchIDFromPerson(person.name);
        return {
          ...person,
           imgID: ID
        };
    }));
};

Upvotes: 1

Related Questions