javascriptreactjsasynchronousyandex-mapsyandex-api

Javascript and async problem (building distance matrix with yandex maps api)


I'm new to js and async/await stuff and I want to build distance matrix using yandex maps api and then optimize the route using ACO algorithm. Everything works, but my distance matrix generating so long because of await for every request in loop. I know that i should avoid it but i have no idea how.

My distance matrix should be done before calling ACO algorithm function.

async function buildDistanceMatrix(ymaps) {
  const n = routeCoords.length;
  let distanceMatrix = [];
  console.log(routeCoordsRef.current);

  for (let i = 0; i < n; i++) {
    let newArr = [];
    for (let j = 0; j < n; j++) {
      await ymaps.route([routeCoordsRef.current[i], routeCoords[j]]).then((route) => {
        newArr.push(route.getLength())
        console.log(i, j, routeCoordsRef.current[i], routeCoords[j], route.getLength());
      });
    }
    distanceMatrix.push(newArr);
  }
  return distanceMatrix;
}


let distanceMatrix = await buildDistanceMatrix(ymaps);
// and here my distance matrix done and i'm calling another function that uses distanceMatrix

Solution

  • I think that you need to take into consideration the following two notes...

    Note 1: Typically when dealing with promises, either use "async" / "await" or stick with the ".then()" syntax, but don't mix them. For example...

    async function buildDistanceMatrix(ymaps) {...
    
        let route = await ymaps.route( [ routeCoordsRef.current[ i ], routeCoords[ j ] ] );
        newArr.push( route.getLength() );
        console.log( i, j, routeCoordsRef.current[ i ], routeCoords[ j ], route.getLength() );
    
    ...}
    

    ...or...

    function buildDistanceMatrix(ymaps) {...
    
        ymaps.route( [ routeCoordsRef.current[ i ], routeCoords[ j ] ] ).then( ( route ) => {
            newArr.push( route.getLength() );
            console.log( i, j, routeCoordsRef.current[ i ], routeCoords[ j ], route.getLength() );
        } );
    
    ...}
    

    Generally speaking, the "async" / "await" is more readable as it provides a syntax that reads procedurally, and helps avoid nesting hell of the then() functions.

    Note 2: It appears that the Yandex router function makes use of a web service...

    ...and hence the need for the promise, as the javascript code essentially suspends awaiting on the promise to resolve once the Yandex server responds.

    Suggest looking into the Promise.all() function...

    ...as this permits your function to initiate a number of promises without waiting for any promises to complete, and only after creating all the promises does your function then await for all the promises to resolve.

    The primary benefit is that the remote server is working on all the requests concurrently.

    Before running off down this path, check on the Yandex terms of service to determine if there are restrictions on the number of concurrent outstanding calls. Additionally, well designed web services will throttle the number of concurrent requests, so there might be some large gains in performance for a small number of web calls, but after a certain limit, the web server throttle kicks in and the calls are then essentially queued (or ignored!) and handled synchronously again...

    EDIT: Use of Promise.all with async / await syntax.

    In short, your function should not await when creating the promises, but instead, capture the promises into an array. Then, once all the promises have been created (in this case, initiating a series of web service calls), then await on the Promise.all() function, passing all the unresolved promises. The resulting array will correspond directly with the order that the promises were passed in Promise.all(). Here is generally how your function will look. Note the addition of parallelArray, which is a means of capturing any ancillary data that you need when later making use of arrayOfResults...

    async function buildDistanceMatrix(ymaps) {...
    
        let arrayOfPromises = [];
        let parallelArray = [];
    
        for ( let i = 0; i < n; i++ ) {
            for (let j = 0; j < n; j++ ) {
                arrayOfPromises.push( ymaps.route( [ routeCoordsRef.current[ i ], routeCoords[ j ] ] ) );
                parallelArray.push( { i: i, j: j, rci: routeCoordsRef.current[ i ], rcj: routeCoords[ j ] );
            }
        }
    
        let arrayOfResults = await Promise.all( arrayOfPromises );
    
    ...}
    

    Hope this helps...