Section critique en JavaScript ou jQuery

J’ai une page Web dans laquelle un certain événement Ajax est déclenché de manière asynchrone. Cette section Ajax peut être appelée une ou plusieurs fois. Je ne contrôle pas le nombre de fois que cet événement est déclenché, ni le minutage.

En outre, il existe un certain code dans cette section Ajax qui doit s’exécuter en tant que section critique, ce qui signifie que, lorsqu’elle est en cours d’exécution, aucune autre copie de ce code ne devrait être en cours d’exécution.

Voici un pseudo code:

  1. Exécuter du code JavaScript ou jQuery
  2. Entrez la section critique qui est Ajax (lorsqu’un certain processus attend un rappel de réponse, ne réinsérez pas cette section tant que ce processus n’est pas terminé)
  3. Exécuter plus de code JavaScript ou jQuery

Ma question est, comment puis-je exécuter l’étape 2 de la manière décrite ci-dessus? Comment créer / garantir une section d’exclusion mutuelle à l’aide de JavaScript ou de jQuery.

Je comprends la théorie (sémaphores, verrous, etc.), mais je ne pouvais pas implémenter de solution utilisant JavaScript ou jQuery.

MODIFIER

Si vous suggérez qu’une variable booléenne pénètre dans la section critique, cela ne fonctionnera pas et les lignes ci-dessous expliqueront pourquoi.

le code d’une section critique serait le suivant (en utilisant les suggestions de variables booléennes):

load_data_from_database = function () { // Load data from the database. Only load data if we almost reach the end of the page if ( jQuery(window).scrollTop() >= jQuery(document).height() - jQuery(window).height() - 300) { // Enter critical section if (window.lock == false) { // Lock the critical section window.lock = true; // Make Ajax call jQuery.ajax({ type: 'post', dataType: 'json', url: path/to/script.php, data: { action: 'action_load_posts' }, success: function (response) { // First do some stuff when we get a response // Then we unlock the critical section window.lock = false; } }); // End of critical section } } }; // The jQuery ready function (start code here) jQuery(document).ready(function() { var window.lock = false; // This is a global lock variable jQuery(window).on('scroll', load_data_from_database); }); 

Il s’agit maintenant du code de la section lock, comme suggéré avec une variable booléenne. Cela ne fonctionnerait pas comme suggéré ci-dessous:

  1. L’utilisateur fait défiler l’ jQuery(window).on('scroll', load_data_from_database); vers le bas (et en fonction de l’association jQuery(window).on('scroll', load_data_from_database); plusieurs événements de défilement sont déclenchés.

  2. Supposons que deux événements de défilement sont déclenchés presque au même moment

  3. Les deux appellent la fonction load_data_from_database

  4. Le premier événement vérifie si window.lock est faux (answer est true, donc si statement est correct)

  5. Le deuxième événement vérifie si window.lock est faux (answer est true, donc si statement est correct)

  6. Le premier événement entre la déclaration if

  7. Le deuxième événement entre la déclaration if

  8. La première instruction définit window.lock sur true

  9. La deuxième instruction définit window.lock sur true

  10. La première instruction exécute la section critique Ajax

  11. La deuxième instruction exécute la section critique Ajax.

  12. Les deux finissent le code

Comme vous le constatez, les deux événements sont déclenchés presque au même moment et entrent tous deux dans la section critique. Donc, un verrou n’est pas possible.

Je pense que l’information la plus utile que vous avez fournie ci-dessus était votre parsing du locking.

  1. L’utilisateur fait défiler l’ jQuery(window).on('scroll', load_data_from_database); vers le bas (et en fonction de l’association jQuery(window).on('scroll', load_data_from_database); plusieurs événements de défilement sont déclenchés.

  2. Supposons que deux événements de défilement sont déclenchés presque au même moment

  3. Les deux appellent la fonction load_data_from_database

  4. Le premier événement vérifie si window.lock est faux (answer est true, donc si statement est correct)

  5. Le deuxième événement vérifie si window.lock est faux (answer est true, donc si statement est correct)

Tout de suite, cela me dit que vous êtes arrivé à un malentendu commun (et assez intuitif).

Javascript est asynchrone, mais le code asynchrone est différent du code concurrent. Autant que je sache, “asynchrone” signifie que les sous-routines d’une fonction ne sont pas nécessairement explorées en profondeur-premier ordre comme on pourrait s’y attendre dans le code synchrone. Certains appels de fonction (ceux que vous appelez “ajax”) seront placés dans une queue et exécutés ultérieurement. Cela peut conduire à un code confus, mais rien n’est aussi déroutant que de penser que votre code asynchrone est exécuté simultanément. La “concurrence” (comme vous le savez) est le moment où des déclarations de différentes fonctions peuvent s’entrelacer.

Des solutions telles que les verrous et les sémaphores ne sont pas la bonne façon de penser au code async. Les promesses sont la bonne voie. C’est ce qui rend la programmation sur le Web amusante et cool.

Je ne suis pas un gourou des promesses, mais voici un violon qui fonctionne (je pense) démontre une solution.

 load_data_from_database = function () { // Load data from the database. Only load data if we almost reach the end of the page if ( jQuery(window).scrollTop() >= jQuery(document).height() - jQuery(window).height() - 300) { console.log(promise.state()); if (promise.state() !== "pending") { promise = jQuery.ajax({ type: 'post', url: '/echo/json/', data: { json: { name: "BOB" }, delay: Math.random() * 10 }, success: function (response) { console.log("DONE"); } }); } } }; var promise = new $.Deferred().resolve(); // The jQuery ready function (start code here) jQuery(document).ready(function() { jQuery(window).on('scroll', load_data_from_database); }); 

J’utilise une promesse globale pour m’assurer que la partie ajax de votre gestionnaire d’événements n’est appelée qu’une fois. Si vous faites défiler le violon de haut en bas, vous verrez que pendant le traitement de la demande ajax, aucune nouvelle demande ne sera faite. Une fois la demande ajax terminée, de nouvelles demandes peuvent être faites à nouveau. Avec un peu de chance, c’est le comportement que vous recherchiez.

Cependant, ma réponse comporte des mises en garde assez importantes: la mise en œuvre des promesses de jQuery est notoirement rompue. Ce n’est pas simplement quelque chose que les gens disent sonner intelligemment, c’est en fait assez important. Je suggérerais d’utiliser une bibliothèque de promesses différente et de la mélanger avec jQuery. Ceci est particulièrement important si vous commencez juste à apprendre sur les promesses.

EDIT : Sur une note personnelle, j’étais récemment dans le même bateau que vous. Il y a à peine trois mois, je pensais que certains gestionnaires d’événements que j’utilisais étaient entrelacés. J’étais stupéfait et incrédule lorsque les gens ont commencé à me dire que javascript est à thread unique. Ce qui m’a aidé, c’est de comprendre ce qui se passe quand un événement est déclenché.

En codage synchrone, nous sums habitués à l’idée d’une “stack” de “frameworks” représentant chacun le contexte d’une fonction. En javascript et dans d’autres environnements de programmation asynchrones, la stack est complétée par une queue. Lorsque vous déclenchez un événement dans votre code ou utilisez une requête asynchrone telle que $.ajax appel $.ajax , vous envoyez un événement à cette queue. L’événement sera traité la prochaine fois que la stack sera vide. Donc, par exemple, si vous avez ce code:

 function () { this.on("bob", function () { console.log("hello"); }) this.do_some_work(); this.sortinggger("bob"); this.do_more_work(); } 

Les deux fonctions do_some_work et do_more_work se déclenchent l’une après l’autre, immédiatement. Ensuite, la fonction se terminera et l’événement que vous avez mis en queue lancera un nouvel appel de fonction (sur la stack) et “hello” apparaîtra dans la console. Les choses se compliquent si vous déclenchez un événement dans votre gestionnaire ou si vous déclenchez un événement dans un sous-programme.

C’est bien beau, mais là où les choses commencent à devenir vraiment nul, c’est quand vous voulez gérer une exception. Au moment où vous entrez dans un pays asynchrone, vous laissez derrière vous le beau serment de “une fonction doit revenir ou lancer”. Si vous êtes dans un gestionnaire d’événements et que vous lancez une exception, où sera-t-il intercepté? Ce,

 function () { try { $.get("stuff", function (data) { // uh, now call that other API $.get("more-stuff", function (data) { // hope that worked... }; }); } catch (e) { console.log("pardon me?"); } } 

ne vous sauvera pas maintenant. Les promesses vous permettent de reprendre ce serment ancien et puissant en vous donnant le moyen d’enchaîner vos rappels et de contrôler où et quand ils reviendront. Donc, avec une belle API de promesses (pas jQuery), vous enchaînez ces rappels de manière à vous permettre de masquer les exceptions comme vous le souhaitez et de contrôler l’ordre d’exécution. Ceci, à mon sens, est la beauté et la magie des promesses.

Quelqu’un m’arrête si je suis totalement hors tension.

Je recommanderais une queue ne permettant qu’un élément à la fois. Cela nécessitera quelques modifications (mais pas beaucoup) à votre fonction critique:

 function critical(arg1, arg2, completedCallback) { $.ajax({ .... success: function(){ // normal stuff here. .... // at the end, call the completed callback completedCallback(); } }); } var queue = []; function queueCriticalCalls(arg1, arg2) { // this could be done abstractly to create a decorator pattern queue.push([arg1, arg2, queueCompleteCallback]); // if there's only one in the queue, we need to start it if (queue.length === 1) { critical.apply(null, queue[0]); } // this is only called by the critical function when one completes function queueCompleteCallback() { // clean up the call that just completed queue.splice(0, 1); // if there are any calls waiting, start the next one if (queue.length !== 0) { critical.apply(null, queue[0]); } } } 

UPDATE: Solution alternative utilisant la promesse de jQuery (requirejs jQuery 1.8+)

 function critical(arg1, arg2) { return $.ajax({ .... }); } // initialize the queue with an already completed promise so the // first call will proceed immediately var queuedUpdates = $.when(true); function queueCritical(arg1, arg2) { // update the promise variable to the result of the new promise queuedUpdates = queuedUpdates.then(function() { // this returns the promise for the new AJAX call return critical(arg1, arg2); }); } 

Oui, la promesse d’un code plus propre a été réalisée. 🙂

Vous pouvez envelopper la section critique dans une fonction, puis l’échanger pour qu’elle ne fasse rien après la première exécution:

  // this function does nothing function noop() {}; function critical() { critical = noop; // swap the functions //do your thing } 

Inspiré par l’utilisateur @I Hate Lazy Fonction en javascript pouvant être appelée une seule fois