Javascript – synchronisation après les appels asynchrones

J'ai un objet Javascript qui nécessite 2 appels sur un serveur externe pour créer son contenu et faire quelque chose de significatif. L'objet est construit de sorte que l'instanciation d'une instance entraînera automatiquement ces 2 appels. Les 2 appels partagent une fonction de rappel habituelle qui fonctionne sur les données renvoyées puis appelle une autre méthode. Le problème est que la prochaine méthode ne devrait pas être appelée tant que les deux méthodes ne sont pas retournées. Voici le code que j'ai mis en œuvre actuellement:

foo.bar.Object = function() { this.currentCallbacks = 0; this.expectedCallbacks = 2; this.function1 = function() { // do stuff var me = this; foo.bar.sendRequest(new RequestObject, function(resp) { me.commonCallback(resp); }); }; this.function2 = function() { // do stuff var me = this; foo.bar.sendRequest(new RequestObject, function(resp) { me.commonCallback(resp); }); }; this.commonCallback = function(resp) { this.currentCallbacks++; // do stuff if (this.currentCallbacks == this.expectedCallbacks) { // call new method } }; this.function1(); this.function2(); } 

Comme vous pouvez le voir, je forcé l'objet à continuer après que les deux appels sont retournés à l'aide d'un compteur simple pour valider qu'ils ont tous deux retourné. Cela fonctionne mais semble être une très mauvaise mise en œuvre. Je n'ai travaillé avec Javascript que quelques semaines maintenant et je me demande s'il existe une meilleure méthode pour faire la même chose sur laquelle je n'ai pas encore trébuché.

Merci pour toute aide.

Il y a à peine un autre moyen que d'avoir ce compteur. Une autre option serait d'utiliser un objet {} et d'ajouter une clé pour chaque demande et de la supprimer si elle est terminée. De cette façon, vous savez immédiatement qui est revenu. Mais la solution reste la même.

Vous pouvez changer un peu le code. Si tel est que dans votre exemple, il vous suffit d'appeler une autre fonction à l'intérieur de commonCallback (je l'ai appelée otherFunction) que vous n'avez pas besoin de CommonCallback. Afin de sauvegarder le contexte, vous avez déjà utilisé des fermetures. Au lieu de

 foo.bar.sendRequest(new RequestObject, function(resp) { me.commonCallback(resp); }); 

Vous pourriez le faire de cette façon

 foo.bar.sendRequest(new RequestObject, function(resp) { --me.expectedCallbacks || me.otherFunction(resp); }); 

Sauf si vous êtes prêt à sérialiser l'AJAX, il n'y a pas d'autre façon que je puisse penser à faire ce que vous proposez. Cela dit, je pense que ce que vous avez est assez bon, mais vous voudrez peut-être nettoyer la structure pour ne pas jeter l'objet que vous créez avec les données d'initialisation.

Voici une fonction qui pourrait vous aider:

 function gate(fn, number_of_calls_before_opening) { return function() { arguments.callee._call_count = (arguments.callee._call_count || 0) + 1; if (arguments.callee._call_count >= number_of_calls_before_opening) fn.apply(null, arguments); }; } 

Cette fonction est ce qu'on appelle une fonction d'ordre supérieur – une fonction qui prend les fonctions comme arguments. Cette fonction particulière renvoie une fonction qui appelle la fonction passée lorsqu'elle a été appelée number_of_calls_before_opening times. Par exemple:

 var f = gate(function(arg) { alert(arg); }, 2); f('hello'); f('world'); // An alert will popup for this call. 

Vous pouvez utiliser ceci comme méthode de rappel:

 foo.bar = function() { var callback = gate(this.method, 2); sendAjax(new Request(), callback); sendAjax(new Request(), callback); } 

Le deuxième rappel, quel qu'il soit, garantira que la method est appelée. Mais cela entraîne un autre problème: la fonction de la gate appelle la fonction passée sans aucun contexte, this qui signifie que l'objet global n'est pas l'objet que vous construisez. Il existe plusieurs façons de contourner cela: vous pouvez soit fermer this faisant appel à me ou à meself . Ou vous pouvez créer une autre fonction de commande supérieure qui fait exactement cela.

Voici le sujet du premier cas:

 foo.bar = function() { var me = this; var callback = gate(function(a,b,c) { me.method(a,b,c); }, 2); sendAjax(new Request(), callback); sendAjax(new Request(), callback); } 

Dans ce dernier cas, l'autre fonction d'ordre supérieur serait comme suit:

 function bind_context(context, fn) { return function() { return fn.apply(context, arguments); }; } 

Cette fonction renvoie une fonction qui appelle la fonction passée dans le contexte passé. Un exemple de ceci serait le suivant:

 var obj = {}; var func = function(name) { this.name = name; }; var method = bind_context(obj, func); method('Your Name!'); alert(obj.name); // Your Name! 

Pour le mettre en perspective, votre code serait le suivant:

 foo.bar = function() { var callback = gate(bind_context(this, this.method), 2); sendAjax(new Request(), callback); sendAjax(new Request(), callback); } 

En tout cas, une fois que vous avez effectué ces refactorings, vous aurez clarifié l'objet en cours de construction de tous ses membres qui ne sont nécessaires qu'à l'initialisation.

Je peux ajouter que Underscore.js a une belle petite aide pour ceci :

Crée une version de la fonction qui ne sera exécutée qu'après avoir été appelée nombre de fois. Utile pour le regroupement de réponses asynchrones, où vous souhaitez vous assurer que tous les appels asynchrones sont terminés avant de continuer .

 _.after(count, function) 

Le code de _after (version 1.5.0):

 _.after = function(times, func) { return function() { if (--times < 1) { return func.apply(this, arguments); } }; }; 

Les informations sur la licence (version 1.5.0)

C'est bon, monsieur Kyle.

Pour en dire un peu plus simple, j'utilise habituellement une fonction Start et a Done.
-La fonction Démarrer prend une liste de fonctions qui seront exécutées.
-La fonction Done est appelée par les rappels de vos fonctions que vous avez passées à la méthode de démarrage.
-De plus, vous pouvez passer une fonction ou une liste de fonctions à la méthode effectuée qui sera exécutée lorsque le dernier rappel sera terminé.

Les déclarations ressemblent à ça.

 var PendingRequests = 0; function Start(Requests) { PendingRequests = Requests.length; for (var i = 0; i < Requests.length; i++) Requests[i](); }; //Called when async responses complete. function Done(CompletedEvents) { PendingRequests--; if (PendingRequests == 0) { for (var i = 0; i < CompletedEvents.length; i++) CompletedEvents[i](); } } 

Voici un exemple simple en utilisant les google maps api.

 //Variables var originAddress = "*Some address/zip code here*"; //Location A var formattedAddress; //Formatted address of Location B var distance; //Distance between A and B var location; //Location B //This is the start function above. Passing an array of two functions defined below. Start(new Array(GetPlaceDetails, GetDistances)); //This function makes a request to get detailed information on a place. //Then callsback with the **GetPlaceDetailsComplete** function function GetPlaceDetails() { var request = { reference: location.reference //Google maps reference id }; var PlacesService = new google.maps.places.PlacesService(Map); PlacesService.getDetails(request, GetPlaceDetailsComplete); } function GetPlaceDetailsComplete(place, status) { if (status == google.maps.places.PlacesServiceStatus.OK) { formattedAddress = place.formatted_address; Done(new Array(PrintDetails)); } } function GetDistances() { distService = new google.maps.DistanceMatrixService(); distService.getDistanceMatrix( { origins: originAddress, destinations: [location.geometry.location], //Location contains lat and lng travelMode: google.maps.TravelMode.DRIVING, unitSystem: google.maps.UnitSystem.IMPERIAL, avoidHighways: false, avoidTolls: false }, GetDistancesComplete); } function GetDistancesComplete(results, status) { if (status == google.maps.DistanceMatrixStatus.OK) { distance = results[0].distance.text; Done(new Array(PrintDetails)); } } function PrintDetails() { alert(*Whatever you feel like printing.*); } 

Donc, en un mot, ce que nous faisons ici est
-Passing un tableau de fonctions à la fonction Start
-La fonction Démarrer appelle les fonctions dans le tableau et définit le nombre de demandes en attente
– Dans les rappels de nos demandes en attente, nous appelons la fonction Done – La fonction Done prend un ensemble de fonctions
-La fonction Done diminue le compteur PendingRequests
– Si ce ne sont plus des demandes en attente, nous appelons les fonctions passées à la fonction Terminé

C'est un exemple simple, mais pratique, de la synchronisation des appels Web. J'ai essayé d'utiliser un exemple de ce qui est largement utilisé, alors je suis allé avec Google maps api. J'espère que quelqu'un trouve cela utile.

Une autre façon serait d'avoir un point de synchronisation grâce à une minuterie. Ce n'est pas beau, mais il a l'avantage de ne pas avoir à ajouter l'appel à la fonction suivante dans le rappel.

Ici, la fonction execute_jobs est le point d'entrée. Il faut une liste de données à exécuter simultanément. Il définit d'abord le nombre d'emplois à attendre jusqu'à la taille de la list . Ensuite, il définit une minuterie pour tester l'état de fin (le nombre tombe à 0). Et enfin, il envoie un travail pour chaque donnée. Chaque emploi réduit le nombre d'emplois attendus par un.

Il ressemblerait à quelque chose comme ça:

 var g_numJobs = 0; function async_task(data) { // // ... execute the task on the data ... // // Decrease the number of jobs left to execute. --g_numJobs; } function execute_jobs(list) { // Set the number of jobs we want to wait for. g_numJobs = list.length; // Set the timer (test every 50ms). var timer = setInterval(function() { if(g_numJobs == 0) { clearInterval(timer); do_next_action(); } }, 50); // Send the jobs. for(var i = 0; i < list.length; ++i) { async_task(list[i])); } } 

Pour améliorer ce code, vous pouvez effectuer des cours Job et JobList . Le Job exécute un rappel et diminue le nombre d'emplois en attente, tandis que JobList la minuterie et appelle le rappel à l'action suivante une fois les travaux terminés.

J'ai partagé la même frustration. Comme j'ai enchaîné plus d'appels asynchrones, il est devenu un rappel. Donc, j'ai trouvé ma propre solution. Je suis sûr qu'il existe des solutions similaires, mais je voulais créer quelque chose de très simple et facile à utiliser. Asynq est un script que j'ai écrit pour créer des tâches asynchrones. Donc, pour exécuter f2 après f1, vous pouvez:

Asynq.run (f1, f2)

Vous pouvez chaîner autant de fonctions que vous le souhaitez. Vous pouvez également spécifier des paramètres ou exécuter une série de tâches sur des éléments dans un tableau aussi. J'espère que cette bibliothèque peut résoudre vos problèmes ou problèmes similaires que d'autres ont.