Home > OS >  Can't break out of async loop
Can't break out of async loop

Time:03-23

I'm trying to iterate a list of objects that I have, and want to check if it has 'Add to Cart' text, then get it's productID and then break out of the loop (since I only want the first available item).

However, I've tried breaking it but it runs through all of the elements regardless, and can't seem it figure out why. Does it have to do with async await? I'm using puppeteer.

    let putterID;
    
    await putters.every(async (putter) => {
        let inStock = await putter.$eval('.product-details .tocart a', el => el.innerHTML);
        inStock = inStock.trim();

        if (inStock == "Add To Cart") {
            putterID = await putter.$eval('.product-details .price', el => el.getAttribute('data-publishproductid'));
            console.log(`Found the first available putter with ID: ${putterID}`);
            return false;
        }

    });

CodePudding user response:

The built-in higher order functions don't play nice with asynchronous functions. You aren't going to be able to get .every() to do what you want like that.

If you want to do a for loop, why not just do a for loop?

for (const putter of putters) {
  let inStock = await putter.$eval('.product-details .tocart a', el => el.innerHTML);
  inStock = inStock.trim();

  if (inStock == "Add To Cart") {
      putterID = await putter.$eval('.product-details .price', el => el.getAttribute('data-publishproductid'));
      console.log(`Found the first available putter with ID: ${putterID}`);
      break;
  }
           
}

The reason why .every() isn't working the way you want is because of the following: .every() takes a synchronous function, calls it, and if all of the returned values are truthy, .every() will return true, otherwise it will return false. You passed in an asyncrounous function, which, remember, an asyncrounous function is really the same as a syncrounous function that returns a promise, that's it. So, for each item in putters, the callback will be called, and the callback is always going to return a promise, which is truthy, causing .every() to return true (which you then await for no reason - true isn't a promise, so awaiting it does nothing). Note that nothing actually waited for these callbacks to finish executing. They'll finish later on, at their own time.

CodePudding user response:

The use of the every method in Array seems to be fully explained by Scotty Jamison. Since the async function returns a return value enclosed in Promise, the return value of the function that is a parameter of the every method in your code is Promise<void|false>, which is evaluated as true.

One more thing, if you use for loop and async/await, you can only handle one task at a time. So you can't benefit from Concurrency. Therefore, you can use the counter as follows to check every end point and get the first result you want.

function getPutterID(putters) {
  return new Promise((res, rej) => {
    let count = putters.length;
    let complete = false;
    let putterID = '';

    async function checkPutter(putter) {
      let inStock = await putter.$eval('.product-details .tocart a', el => el.innerHTML);
      inStock = inStock.trim();
      count--;

      if (inStock == "Add To Cart" && !complete) {
        complete = true;

        console.log(`Found the first available putter with ID: ${putterID}`);  
        res(putter.$eval('.product-details .price', el => el.getAttribute('data-publishproductid')));          
      }
      if (!count) rej();
    }

    putters.forEach(putter => checkPutter(putter));
  });
}

const putterID = await getPutterID(putters);

I created this code assuming that the putterID will be obtained unconditionally, but it is not complete, so it needs to be modified according to the situation. For example, if there are too many putters, you may need to control the number of async functions that run simultaneously on the inStock check.

  • Related