Search code examples
javascriptasynchronouspromiseasync-awaitarray-map

Why does using async-await in map function still return promises and not the resolved values?


I have an API call to an array of links and I want to use a map function to save al the responses

const allImages = list.map(async (imageLink) => {
  const image = await axios.get(imageLink);
  return image.data.primaryImage;
});

Why allImages is still an array of promises and not an array of responses?


Solution

  • Because Array.map method itself is not an async method. In fact, I must remember you that JavaScript is a single threaded language. The only operations that can really happen simultaneously are I/O operations handled by your platform.

    With that being said, in your case, Array.map will loop through your whole array and will return a new array containing the returned values from your callback for each original item.

    An Async function always immediately and implicitly returns a promise that will then be resolved to the explicitly returned value of your function.

    That is why you get an array of promises.

    What you can do then, if you want to use the responses, is something like this:

    const allImages = list.map(async (imageLink) => {
        const image = await axios.get(imageLink);
        return image.data.primaryImage;
    });
    
    Promise.all(allImages).then((responses) => {
        console.log(responses); // THIS ARE THE RESPONSES YOU ARE LOOKING FOR.
    });
    

    Please let me know if you have any other doubt.

    In response to comments:

    JS code execution can never be paused, it would freeze your engine and would break the entire purpose of JS async implementation.

    When you are inside an async function and find an await statement, code execution 'jumps' outside of the async scope 'promising' to resume where it left as soon as the Promise related to the await statement is fulfilled.

    JS async/await is just syntactic sugar to deal with promises. And promises are just syntactic sugar to deal with callbacks.

    See the following example using your code:

    let count = 0;
    
    const allImages = list.map(async (imageLink) => {
        const image = await axios.get(imageLink);
    
        // CODE EXECUTION 'STOPS' HERE, BUT CONTINUES OUTSIDE OF THE ASYNC SCOPE. Array.map PASSES TO NEXT ITEM.
        count++;
        console.log('A' + count);
    
        return image.data.primaryImage; // THIS RESOLVES THE PROMISE.
    });
    
    // CODE EXECUTION CONTINUES
    console.log('B');
    
    Promise.all(allImages).then(() => {
        // ALL PROMISES HAVE BEEN FULFILLED.
        console.log('C');
    });
    
    /*
    
    EXPECTED OUTPUT ORDER:
    
    B
    A1, A2, A3... (depending on http responses arrival order)
    C
    
    */
    

    Hopefully a more intuitive example that should work as you expect:

    (async () => {
        for (let i = 0; i < list.length; i++) {
            const imageLink = list[i];
            const image = await axios.get(imageLink);
            // CODE EXECUTION 'STOPS' HERE.
            list[i] image.data.primaryImage;
        }
    
        console.log(list); // AN ARRAY OF RESPONSES.
    })();
    
    // HOWEVER, CODE EXECUTION NEVER REALLY STOPS AND CONTINUES HERE. OUTSIDE OF THE async SCOPE.