简体   繁体   中英

Making sense of asynchronous javascript (google maps)


MY ANSWER BELOW HAS A WORKING CODE EXAMPLE AND AN EXPLANATION


I am trying to get my head around asynchronous programming by creating a Traveling Salesperson algorithm with the help of the Google maps API.

Here is how it should work:

  1. We have a list of address strings addresses
  2. For every combination of address [i] and [j] we calculate the distance, using google directionsService and we store this in a Javascript object ( distanceTable ).
  3. The tspSolver should run after we have all the distances (not implemented yet).

My naive code is below. As you can see there are several issues, all of which have to do with callbacks:

I pass directionResults as a callback to the directionsService. It correctly calculates the distance, but since I am no longer inside the loop to construct the distanceTable, I cannot store the result correctly. I commented out the section which should store it, but that obviously only works inside the loop.

I pass my tspSolver as a callback for allDistances . However I notice that it gets executed before the distances are calculated by directionResults . My guess is that I have to do some form of nesting of callbacks?

Who can help me make sense of this.

gMap.directionsService = new google.maps.DirectionsService();

var addresses = ['Dam, Amsterdam','Spui, Amsterdam','Middenweg, Amsterdam'];
var distanceTable = {};
//all combinations of addresses with distances, for use in TSP algorithm
//{
//  addressA {
//    addressB: 2000
//    addressC: 2500
//  }
//  addressB {
//    addressC: 1800
//  }


function tspSolver(distanceTable) {
  console.log('Distances are there, now for some clever TSP algorithm')
  //this should only be executed after the distances are returned.
}

function allDistances(addresses, callback) {
  for(var i=0; i<addresses.length; ++i) {
    distanceTable[addresses[i]] = {};
    for(var j=i+1; j<addresses.length; ++j) {
      // Compose request for every pair of addresses (one way)
      var request = {
        origin: addresses[i],
        destination: addresses[j],
        travelMode: 'DRIVING'
      };
      console.log(request);
      gMap.directionsService.route(request, directionResults);
    }
  }
  callback(distanceTable)
}


function directionResults(result, status) {
  console.log("Receiving request for route");
  console.log(result);
  if (status == google.maps.DirectionsStatus.OK) {
    var totalDistance = 0;
    var legs = result.routes[0].legs;
    for(var i=0; i<legs.length; ++i) {
      totalDistance += legs[i].distance.value;
    }
    console.log(totalDistance);
    // I really want to add it to the distanceTable...
    //distanceTable[addresses[i]][addresses[j]] = totalDistance;
  }
}


//call function to start solving
function executeTSP() {
  allDistances(addresses, tspSolver);
}

I would assume you need to pass callback with request

Callback is the way to continue execution when you are programming in async.

Pseudo example:

function doAcync(callback)
{
 //gets result
 callback(result);
}

function one()
{
  doAcync(two);
}

function two(result)
{
  doAcync(three);
}

function three(result)
{
  // continue with your live
}

The solution to my first problem lies in "closures inside loops". I am tracking my loop with a counter (rather 2 counters in my case). By the time the asynchronous function/closure is executed, the loop would have completed.

That would defeat all attempts to access the loop counters from within the closure: you would always get the maximum number.

The solution I chose was to wrap the closure in another function: a so called "anonymous wrapper". This wrapper hold a reference which is not affected by the loop.

Some better explanations can be found here: Mutable variable is accessible from closure. How can I fix this? And here: http://bonsaiden.github.io/JavaScript-Garden/#function.closures

To get the second problem (execution order) fixed, I resorted to doing a callback within the callback. Some admittedly ugly code keeps track that the callback is only called on the last iteration. I am sure there are more elegant ways to deal with this, but for now it works:

var directionsService;

function initialize() {
  directionsService = new google.maps.DirectionsService();
}

var addresses = ['Dam, Amsterdam','Spui, Amsterdam','Middenweg, Amsterdam'];
var distanceTable = {};
//all combinations of addresses with distances, for use in TSP algorithm
//{
//  addressA {
//    addressB: 2000
//    addressC: 2500
//  }
//  addressB {
//    addressC: 1800
//  }


function tspSolver(distances) {
  //this function is called as a callback from allDistances
  for(var i=0; i<addresses.length; ++i) {
    for(var j=i+1; j<addresses.length; ++j) {
      console.log(addresses[i]+' to '+addresses[j]+': '+distances[addresses[i]][addresses[j]])
    }
  }
}

function allDistances(addresses, callback) {
  for(var i=0; i<addresses.length; ++i) {
    distanceTable[addresses[i]] = {};
    for(var j=i+1; j<addresses.length; ++j) {
      var request = {
        origin: addresses[i],
        destination: addresses[j],
        travelMode: 'DRIVING'
      };
      console.log(request);
      //anonymous wrapper around closure to preserve the loop counters: i,j -> e,f
      (function(e,f) {directionsService.route(request, function(result,status) {
          if (status == google.maps.DirectionsStatus.OK) {
            //calculate totalDistance as sum of the legs and store the result
            var totalDistance = 0;
            var legs = result.routes[0].legs;
            for(var x=0; x<legs.length; ++x) {
              totalDistance += legs[x].distance.value;
            }
            distanceTable[addresses[e]][addresses[f]] = totalDistance;
          }
        //trigger the callback on the last iteration only
        if (e+2 == addresses.length)
          {
            callback(distanceTable)
          }
        });
      })(i,j);
      //end of wrapper
    }
  }
}

function executeTSP(){
  allDistances(addresses, tspSolver)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM