Search code examples
javascriptpromiseasync-await

Why does async array map return promises, instead of values


See the code below

var arr = await [1,2,3,4,5].map(async (index) => { 
    return await new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(index);
            console.log(index);
        }, 1000);
    });
});
console.log(arr); // <-- [Promise, Promise, Promise ....]
// i would expect it to return [1,2,3,4,5]

Quick edit: The accepted answer is correct, by saying that map doesnt do anything special to async functions. I dont know why i assumed it recognizes async fn and knows to await the response.

I was expecting something like this, perhaps.

Array.prototype.mapAsync = async function(callback) {
    arr = [];
    for (var i = 0; i < this.length; i++)
        arr.push(await callback(this[i], i, this));
    return arr;
};

var arr = await [1,2,3,4,5].mapAsync(async (index) => { 
    return await new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(index);
            console.log(index);
        }, 1000);
    });
});
// outputs 1, 2 ,3 ... with 1 second intervals, 
// arr is [1,2,3,4,5] after 5 seconds.

Solution

  • Because an async function always returns a promise; and map has no concept of asynchronicity, and no special handling for promises.

    But you can readily wait for the result with Promise.all:

    try {
        const results = await Promise.all(arr);
        // Use `results`, which will be an array
    } catch (e) {
        // Handle error
    }
    

    Live Example:

    var arr = [1,2,3,4,5].map(async (index) => { 
        return await new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(index);
                console.log(index);
            }, 1000);
        });
    });
    (async() => {
        try {
            console.log(await Promise.all(arr));
            // Use `results`, which will be an array
        } catch (e) {
            // Handle error
        }
    })();
    .as-console-wrapper {
      max-height: 100% !important;
    }

    or using Promise callbacks:

    Promise.all(arr)
        .then(results => {
            // Use `results`, which will be an array
        })
        .catch(err => {
            // Handle error
        });
    

    Live Example:

    var arr = [1,2,3,4,5].map(async (index) => { 
        return await new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(index);
                console.log(index);
            }, 1000);
        });
    });
    Promise.all(arr)
        .then(results => {
            console.log(results);
        })
        .catch(err => {
            // Handle error
        });
    .as-console-wrapper {
      max-height: 100% !important;
    }


    Side note: Since async functions always return promises, and the only thing you're awaiting in your function is a promise you create, it doesn't make sense to use an async function here anyway. Just return the promise you're creating:

    var arr = [1,2,3,4,5].map((index) => { 
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(index);
                console.log(index);
            }, 1000);
        });
    });
    

    Of course, if you're really doing something more interesting in there, with awaits on various things (rather than just on new Promise(...)), that's different. :-)