web-dev-qa-db-fra.com

JavaScript ne semble pas attendre les valeurs de retour

Je lutte avec ça depuis un moment maintenant. Je suis nouveau sur Javascript et j'ai l'impression que le code que j'ai écrit fonctionne de manière asynchrone. Voici un exemple générique:

Je lance du code dans la fonction a. La fonction A appelle ensuite la fonction B, qui doit retourner une variable à A pour que A puisse l'utiliser dans ses opérations ultérieures. Il semble cependant que lorsque A appelle B, il continue à exécuter son propre code, sans attendre bloqué pour sa valeur de retour, et B n'est pas assez rapide pour que A finisse par atteindre le point où il aurait dû utiliser le retour valeur et j'obtiens une erreur de type de variable non définie.

La façon dont j'ai travaillé autour de cela est d'avoir la fonction A appeler la fonction B qui appelle ensuite une fonction C qui ferait ce que les opérations ultérieures que A ferait avec la valeur de retour .... Je sorte de sérialiser mon code via des appels au lieu de retours ... c'est lourd cependant ...

Voici un exemple où cela se produit dans le code réel:

function initialize() {
    //Geocode Address to obtin Lat and Long coordinates for the starting point of our map
    geocoder = new google.maps.Geocoder();
    var results = geocode(geocoder);
    makeMap(results[0].geometry.location.lat(), results[0].geometry.location.lng());

}

function geocode(geocoder) {
    //do geocoding here...

    var address = "3630 University Street, Montreal, QC, Canada";
    geocoder.geocode({ 'address': address }, function (results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
           return results;
            }
         else {
            alert("Geocode was not successful for the following reason: " + status);
        }
   });

}

function makeMap(lat, long) {
  //  alert(lat); for debuging
    var mapOptions = {
        center: new google.maps.LatLng(lat, long),
        zoom: 17,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    };
     map = new google.maps.Map(document.getElementById("map_canvas"),
        mapOptions);
}

Note: initialize est appelé par body onload = "initialize ()" dans mon html.

Le problème est donc que le makeMap nécessite les valeurs de lat et de longitude obtenues par la fonction Geocode, mais je reçois une erreur dans la console disant que les résultats ne sont pas définis. Que se passe-t-il? Je viens de Java donc je suis un peu confus sur la façon dont le flux de données se passe ici dans JS! Ce seront de précieuses leçons pour l'avenir!

Sur une question secondaire: Comment dois-je diviser mes fonctions sur des scripts externes? Qu'est-ce qui est considéré comme une bonne pratique? toutes mes fonctions doivent-elles être entassées dans un seul fichier .js externe ou dois-je regrouper des fonctions similaires?

26
Georges Krinker

Vous semblez avoir une bonne compréhension du problème, mais il semble que vous ne connaissiez pas la manière de le résoudre. La façon la plus courante de résoudre ce problème consiste à utiliser un rappel. Il s'agit essentiellement de la manière asynchrone d'attendre une valeur de retour. Voici comment vous pouvez l'utiliser dans votre cas:

function initialize() {
    //Geocode Address to obtin Lat and Long coordinates for the starting point of our map
    geocoder = new google.maps.Geocoder();
    geocode(geocoder, function(results) {
        // This function gets called by the geocode function on success
        makeMap(results[0].geometry.location.lat(), results[0].geometry.location.lng());        
    });
}

function geocode(geocoder, callback) {
    //do geocoding here...

    var address = "3630 University Street, Montreal, QC, Canada";
    geocoder.geocode({ 'address': address }, function (results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
            // Call the callback function instead of returning
            callback(results);
        } else {
            alert("Geocode was not successful for the following reason: " + status);
        }
   });

}

...
35
jncraton

J'ai ... l'impression que le code que j'écris s'exécute de manière asynchrone.

Oui. Votre fonction geocodene peut pas renvoyer les résultats de l'appel à l'API Google, car la fonction revient avant la fin de l'appel Google. Voir note ci-dessous:

function geocode(geocoder) {
    //do geocoding here...

    var address = "3630 University Street, Montreal, QC, Canada";
    geocoder.geocode({ 'address': address }, function (results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
           // +---------- This doesn't return anything from your
           // v           geocode function, it returns a value from the callback
           return results;
            }
         else {
            alert("Geocode was not successful for the following reason: " + status);
        }
   });
}

Au lieu de cela, vous devez coder votre fonction geocode afin qu'elle accepte un rappel qu'elle appellera lorsqu'elle aura les résultats. Par exemple.:

// Added a callback arg ---v
function geocode(geocoder, callback) {
    //do geocoding here...

    var address = "3630 University Street, Montreal, QC, Canada";
    geocoder.geocode({ 'address': address }, function (results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
           // v---------- Call the callback
           callback(results);
            }
         else {
            alert("Geocode was not successful for the following reason: " + status);
            callback(null); // <--- Call the callback with a flag value
                            // saying there was an error
        }
   });
}

Ensuite, au lieu de l'utiliser comme ceci:

var results = geocode(someArgHere);
if (results) {
    doSomething(results);
}
else {
    doSomethingElse();
}

Vous l'appelez comme ceci:

geocode(someArgHere, function() {
    if (results) {
        doSomething(results);
    }
    else {
        doSomethingElse();
    }
});

Par exemple, vous allez complètement asynchrone.

12
T.J. Crowder

L'instruction return à l'intérieur de la fonction anonyme renvoie la fonction anonyme, pas la fonction de géocodage externe. La fonction de géocodage renvoie undefined. La méthode geocoder.geocode peut appeler la fonction anonyme quand elle le souhaite, synchroniser ou asynchroniser. Vérifiez les documents pour cela.

1
Douglas

En effet, vous avez raison de vous rendre compte que les appels sont asynchrones et que vous n'obtenez pas une valeur de retour appropriée.

Normalement, lorsque des fonctions sont appelées en js, elles sont synchrones.

e.g. a() calls b(), and a() waits until b() to finish before continuing.

Cependant, dans certaines situations, telles que les appels ajax ou jsonp, cela se fait de manière asynchrone. C'est précisément ce qui se passe lorsque vous appelez geocode().

Votre exécution:

initialize() is called;
initialize() calls geocoder();
geocoder makes a request to Google, and returns null in the meantime.
initialze() calls makemap()
the Google geocoder returns at some point, and executed the success callback, which you have defined as "return results;", but there is nothing to return, since the function has already ended.

Donc, en particulier, utilisez le rappel qui est déjà intégré à l'appel du géocodeur:

if (status == google.maps.GeocoderStatus.OK) {
    makeMap(results);
}
1
Julian H. Lam