bp123
bp123

Reputation: 3417

Map with async returns Promise

The code below is returning [Promise, Promise, Promise, Promise, Promise] and I'm not sure what I'm doing wrong here. What am I missing in this async function?

 async function testFunction(page) {

  async function multipleTests(test) {
    const tests = Array.from(test.querySelectorAll("li"));

    const allTests = tests.map((test, index) => {
      return {
        title: test.querySelector("h3").innerText
      };
    });
    return allTests;
  }


    const tests = Array.from(document.querySelectorAll("section.test")).map(async test => {
      if (test.querySelector("ul li")) {
        const tests = await multipleTests(test);
        return tests;
      }
    })

    console.log(tests);

}

Upvotes: 0

Views: 142

Answers (1)

jfriend00
jfriend00

Reputation: 707318

First off, assuming this is normal browser code, I don't see anything in the .map() part of the function that is asynchronous at all. So, you shouldn't be using async there.

Second, ALL async functions return a promise. So when you do this:

const allTests = test.map(async () => {...});

You will ALWAYS end up with an array of promises. If you want to get the values from those promises, you would have to use Promise.all() as in:

const allTests = Promise.all(test.map(async () => {...});)

But, if nothing in the .map() callback is asynchronous, then there is no need for promises or async here at all - remove the async from the .map() entirely.

const allTests = test.map(() => {...});

Then, third in the second .map(), it again looks like everything is synchronous. I think you can simplify all the code to synchronous like this:

function testFunction(page) {

    function multipleTests(test) {
        const tests = Array.from(test.querySelectorAll("li"));

        const allTests = tests.map((test, index) => {
            return {
                title: test.querySelector("h3").innerText
            };
        });
        return allTests;
    }


    const tests = Array.from(document.querySelectorAll("section.test")).map(test => {
        if (test.querySelector("ul li")) {
            return multipleTests(test);
        } else {
            // return something else here or your tests array will have an undefined value
        }
    })

    console.log(tests);

}

Some points of advice to consider:

  1. Do not complicate things by using async, await or promises in segments of purely synchronous code. While async/await helps people write asynchronous code more easily than without, you're still dealing with promises and getting all results with await or eventually with .then() whereas synchronous code can just return or compute a value directly.
  2. ALL async functions return a promise. Always. The calling code will have to use await or .then() on the returned promise to get access to the value.
  3. .map() (long with all the array iterator methods) is NOT async or promise saavy. So, when you declare an async callback for .map() that callback always returns a promise so you always end up with an array of promises. Sometimes that's useful and what is needed, but you will then have to use something like Promise.all() to know when all those promises have resolved and when you can get all their values.
  4. Many times with asynchronous code, it's simpler to use async and await in a plain for loop because the for construct starting in ES6 is await saavy and it will pause the loop for await making your code much simpler to write.
  5. Your tests computation in the second part of the function looks like odd logic. You first do a document.querySelectorAll("section.test") and then you again look for test.querySelector("ul li"). It seems like you could just do one selector originally document.querySelectorAll("section.test ul li") and avoid two separate operations.

Upvotes: 3

Related Questions