0

I am new to asynchronous operations in JS using promises or async/await and I am trying to wrap my head around the concept. One issue I've come across is how to maintain a state while looping over multiple arrays and executing an asynchronous operation on each of the elements. For example, I have the following code:

const listOfStores = [{location: '123 testers lane'}, {location: '321 testers lane'}];
const listofTechs = [{location: '123 nowhere lane'}];

for(let i = 0; i < listOfStores.length; i++){
  let store = listOfStores[i];
  
  let bestDistance = Infinity;
  for(let j = 0; j < listOfTechs.length; j++){
    let tech = listOfTechs[j];
    
    let distance = await getDistance(store.location, tech.location);
    
    bestDistance = Math.min(bestDistance, distance);
    
  }
  
  console.log('Closest Tech Is ' + bestDistance + ' Miles Away')
}

async function getDistance(store, tech) {
  return new Promise(function(resolve, reject){
    // THIS WOULD ACTUALLY BE AN AJAX CALL TO AN API
    window.setTimeout(function(){
      resolve( Math.floor(Math.random() * (100 - 25) + 25));
    }, 2000);
  });
}

While the above code suits my purpose I am trying to get a better understanding of Promise-based code in general. I realize that the loops must be synchronous in order for the logic to work, but API calls are inherently asynchronous. I am not trying to create asynchronous code, but rather I am trying to deal with asynchronous code. Pausing the code execution and waiting for the promise to resolve with await works well. And it is my understanding that Promise-based code can be rewritten as async/await code and vice versa so my question is how can I rewrite the code above without using async/await? As far as I can tell there is no way to pause execution or maintain a state with regular promises.

This problem seems common enough that someone would have developed a pattern for dealing with it already but so far I have not been able to find that pattern.

Note: Each loop must finish before moving onto the next. I considered looping through the stores and techs arrays and creating a promise that would eventually resolve to the distance between that tech and store, and then store each of these promises in an array, and using Promise. all() to resolve all the individual promises. But this will not work. First, because there could potentially be thousands of stores, and resolving each one at the same time will cause issues with rate limiting. Second Promise. all() will fail if one of the promises is rejected since there could be thousands of promises one is bound to fail and cause all the rest to fail as well.

Any advice is greatly appreciated

Thanks

yCombinator
  • 113
  • 4
  • 15
  • You were on the right path thinking of Promise.all, but take a look on Promise.allSetled instead. If you deal with thousands of items, you may also want to check something like p-limit or p-queue from promise-fun package (https://www.npmjs.com/package/promise-fun) – peter Mar 02 '21 at 23:08
  • What problem in the code you show do you need help with? As it shows how, the loop will pause for each call to `getDistance()` to resolve it's promise and the code after the loop won't run until the loop is done and until the last call to `getDistance()` in the last iteration of the loop is done. So, what problem do you need help with? – jfriend00 Mar 02 '21 at 23:38
  • "*how can I rewrite the code above without using async/await?*" - using recursion instead of loops, you can use the promise `.then` method to chain. Or go all the way (back) to continuation passing style. – Bergi Mar 02 '21 at 23:44
  • @jfiend00 There is no problem, it works fine, but I am trying to understand how I could do the same thing without using async/await or if it is even possible to do that using vanilla js – yCombinator Mar 02 '21 at 23:47
  • @user12206763 - Why do you want to do it without `async/await`? It's doable, but not as easy to code. It's the way we did things before there was `async/await`. You have to either use a third party library to manage the loop for you or you have to code your own loop repeat mechanism without using `for`. – jfriend00 Mar 03 '21 at 00:00

1 Answers1

1

My question is how can I rewrite the code above without using async/await? As far as I can tell there is no way to pause execution or maintain a state with regular promises.

Without async/await it is less simple to write and I know of no reason to do it that way other than curiosity (async/await was added for a reason - to make some types of asynchronous much easier to write). It's the way we did things before there was async/await and you would either use a third party library to manage the looping for you (such as the Bluebird or Async libraries) or you would code your own repeat mechanism without using language loop constructs.

Here's an example where I create a utility function for doing asychronous iteration of an array:

// utility function for doing an asynchronous iteration of an array
function iterateArrayAsync(array, fn) {
    let index = 0;

    function next(val) {
        if (index < array.length) {
            return fn(array[index++]).then(next);
        }
        return val;
    }
    return Promise.resolve(next());
}

function calcBestDistance() {
    const listOfStores = [{ location: '123 testers lane' }, { location: '321 testers lane' }];
    const listOfTechs = [{ location: '123 nowhere lane' }];

    let bestDistance = Infinity;

    return iterateArrayAsync(listOfStores, (store) => {
        return iterateArrayAsync(listOfTechs, (tech) => {
            // process store, tech combination here
            return getDistance(store.location, tech.location).then(distance => {
                bestDistance = Math.min(bestDistance, distance);
            });
        });
    }).then(() => {
        // make resolved value be the bestDistance
        console.log('Closest Tech Is ' + bestDistance + ' Miles Away')
        return bestDistance;
    });
}

function getDistance(store, tech) {
    return new Promise(function(resolve, reject) {
        // THIS WOULD ACTUALLY BE AN AJAX CALL TO AN API
        setTimeout(function() {
            resolve(Math.floor(Math.random() * (100 - 25) + 25));
        }, 500);
    });
}

calcBestDistance().then(result => {
    console.log(result);
}).catch(err => {
    console.log(err);
});

This problem seems common enough that someone would have developed a pattern for dealing with it already but so far I have not been able to find that pattern.

async/await was invented to make this code a lot easier to write. Before that, there were numerous libraries that contained asynchronous management utility function such as Bluebird and Async or (as shown here, people wrote their own mechanisms). The point of async/await was to build into the language an easier way to sequence multiple asynchronous operations and it does often make the code a lot easier to write.


This type of coding (shown above) runs one asynchronous operation at a time (runs them serially where the 2nd doesn't start until the 1st completes). Sometimes, you may want to run up to N operations in parallel to get better end-to-end performance, but because your array may be large, you can't run all of them in parallel like Promise.all() would typically be used for. For operations like that, there are snippets of code such as mapConcurrent() in this answer that iterate the array with up to N operations in flight at the same time. Or, there's rateLimitMap() in this answer that launches requests at no more than a certain number of requests/second.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • @user12206763 - Added links to a few more advances pieces of code [`mapConcurrent()`](https://stackoverflow.com/questions/46654265/promise-all-consumes-all-my-ram/46654592#46654592) and [`rateLimitMap()`](https://stackoverflow.com/questions/36730745/choose-proper-async-method-for-batch-processing-for-max-requests-sec/36736593#36736593) which both let you run a controlled number of requests in parallel (without doing all of them in parallel). Each of these offers a different way to control how many requests are running in parallel (one by qty, one by time). – jfriend00 Mar 03 '21 at 02:46