Reputation: 3080
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:
addresses
distanceTable
).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);
}
Upvotes: 0
Views: 195
Reputation: 3080
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)
}
Upvotes: 1
Reputation: 61401
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
}
Upvotes: 1