Question

Voici le scénario:

On présente à mes utilisateurs une grille, essentiellement une version simplifiée d'un tableur. Il y a des zones de texte dans chaque ligne de la grille. Lorsqu'ils modifient une valeur dans une zone de texte, j'effectue une validation de leur entrée, met à jour la collection qui gère la grille et redessine les sous-totaux de la page. Tout cela est géré par l'événement OnChange de chaque zone de texte.

Quand ils cliquent sur le bouton "Enregistrer". bouton, j'utilise l'événement OnClick du bouton pour effectuer une validation finale des montants, puis envoie l'ensemble de leurs données à un service Web, en les enregistrant.

Du moins, c’est ce qui se produit s’ils passent d’un formulaire au bouton Envoyer.

Le problème est que, s'ils entrent une valeur, puis cliquent immédiatement sur le bouton d'enregistrement, SaveForm () commence à s'exécuter avant la fin de UserInputChanged () - une condition de concurrence critique. Mon code n'utilise pas setTimeout, mais je l'utilise pour simuler le code de validation Sluggish UserInputChanged:

 <!-- snip -->
 <script>
    var amount = null;
    var currentControl = null;

    function UserInputChanged(control) {
        currentControl = control;
        // use setTimeout to simulate slow validation code (production code does not use setTimeout)
        setTimeout("ValidateAmount()", 100); 
    }

    function SaveForm() {
        // call web service to save value
        document.getElementById("SavedAmount").innerHTML = amount;
    }

    function ValidateAmount() {
        // various validationey functions here
        amount = currentControl.value; // save value to collection
        document.getElementById("Subtotal").innerHTML = amount; // update subtotals

    }
</script>
<!-- snip -->
Amount: <input type="text" id="UserInputValue" onchange="UserInputChanged(this);" /> <br />
Subtotal: <span id="Subtotal"></span> <br />
<input type="button" onclick="SaveForm();" value="Save" /> <br /><br />
Saved amount: <span id="SavedAmount"></span>
<!-- snip -->

Je ne pense pas pouvoir accélérer le code de validation. Il est assez léger, mais apparemment, il est suffisamment lent pour que le code tente d'appeler le service Web avant la fin de la validation.

Sur ma machine, ~ 95ms est le nombre magique qui indique si le code de validation est exécuté avant le début du code de sauvegarde. Cela peut être supérieur ou inférieur en fonction de la vitesse de l'ordinateur des utilisateurs.

Quelqu'un at-il des idées sur la façon de gérer cette maladie? Un collègue a suggéré d'utiliser un sémaphore pendant que le code de validation est en cours d'exécution et une boucle occupée dans le code de sauvegarde pour attendre que le sémaphore se déverrouille - mais j'aimerais éviter d'utiliser une sorte de boucle occupée dans mon code.

Était-ce utile?

La solution

Utilisez le sémaphore (appelons-le StillNeedsValidating). si la fonction SaveForm voit que le sémaphore StillNeedsValidating est en place, demandez-lui d'activer son deuxième sémaphore (que j'appellerai FormNeedsSaving ici) et de le retourner. Lorsque la fonction de validation est terminée, si le sémaphore FormNeedsSaving est actif, il appelle la fonction SaveForm par elle-même.

Dans jankcode;

function UserInputChanged(control) {
    StillNeedsValidating = true;
    // do validation
    StillNeedsValidating = false;
    if (FormNeedsSaving) saveForm(); 
}

function SaveForm() {
    if (StillNeedsValidating) { FormNeedsSaving=true; return; }
    // call web service to save value
    FormNeedsSaving = false;
}

Autres conseils

Désactivez le bouton de sauvegarde pendant la validation. Définissez-le sur désactivé comme le ferait la validation en premier lieu, puis réactivez-le à la fin.

par exemple

function UserInputChanged(control) {
    // --> disable button here --< 
    currentControl = control;
    // use setTimeout to simulate slow validation code (production code does not use setTimeout)
    setTimeout("ValidateAmount()", 100); 
}

et

function ValidateAmount() {
    // various validationey functions here
    amount = currentControl.value; // save value to collection
    document.getElementById("Subtotal").innerHTML = amount; // update subtotals
    // --> enable button here if validation passes --<
}

Vous devrez vous adapter lorsque vous supprimez setTimeout et faites de la validation une fonction, mais à moins que vos utilisateurs aient des réflexes surhumains, vous devriez être prêt à partir.

Je pense que le délai d'expiration est la cause de votre problème ... s'il s'agit d'un code clair (pas d'appels AJAX asynchrones, de délais d'expiration, etc.), je ne pense pas que SaveForm sera exécuté avant la fin de UserInputChanged.

Un sémaphore ou un mutex est probablement la meilleure solution, mais au lieu d’une boucle occupée, utilisez simplement setTimeout () pour simuler une veille de thread. Comme ceci:

busy = false;

function UserInputChanged(control) {
    busy = true;
    currentControl = control;
    // use setTimeout to simulate slow validation code (production code does not use setTimeout)
    setTimeout("ValidateAmount()", 100); 
}

function SaveForm() {
    if(busy) 
    {
       setTimeout("SaveForm()", 10);
       return;
    }

    // call web service to save value
    document.getElementById("SavedAmount").innerHTML = amount;
}

function ValidateAmount() {
    // various validationey functions here
    amount = currentControl.value; // save value to collection
    document.getElementById("Subtotal").innerHTML = amount; // update subtotals
    busy = false;
}

Vous pouvez configurer une fonction récurrente qui surveille l'état de la grille entière et déclenche un événement indiquant si la grille entière est valide ou non.

Votre bouton "soumettre le formulaire" s'active ou se désactive lui-même en fonction de cet état.

Oh, je vois une réponse similaire maintenant - cela fonctionne aussi, bien sûr.

Lorsque vous travaillez avec des sources de données asynchrones, vous pouvez certainement rencontrer des conditions de concurrence, car le thread de processus JavaScript continue à exécuter des directives pouvant dépendre des données qui n'ont pas encore été renvoyées par la source de données distante. C'est pourquoi nous avons des fonctions de rappel.

Dans votre exemple, l'appel du code de validation doit avoir une fonction de rappel qui peut faire quelque chose lorsque la validation est renvoyée.

Toutefois, lorsque vous créez quelque chose avec une logique compliquée ou que vous essayez de dépanner ou d'améliorer une série de callbacks existants, vous pouvez devenir fou.

C'est la raison pour laquelle j'ai créé la bibliothèque proto-q: http://code.google. .com / p / proto-q /

Vérifiez si vous faites beaucoup de ce type de travail.

Vous n'avez pas de condition de concurrence critique, les conditions de concurrence ne peuvent pas se produire en javascript car le javascript est à thread unique, de sorte que 2 threads ne peuvent pas interférer l'un avec l'autre.

L'exemple que vous donnez n'est pas un très bon exemple. L'appel setTimeout mettra la fonction appelée dans une file d'attente dans le moteur javascript et l'exécutera plus tard. Si, à ce moment-là, vous cliquez sur le bouton Enregistrer, la fonction setTimeout ne sera appelée qu'après la fin de la sauvegarde.

Ce qui se passe probablement dans votre javascript, c'est que l'événement onClick est appelé par le moteur javascript avant l'appel de l'événement onChange.

Comme indice, n'oubliez pas que javascript est à thread unique, sauf si vous utilisez un débogueur javascript (firebug, débogueur Microsoft screipt). Ces programmes interceptent le fil et le mettent en pause. À partir de ce moment, d'autres threads (via des événements, des appels setTimeout ou des gestionnaires XMLHttp) peuvent alors s'exécuter, ce qui donne à penser que javascript peut exécuter plusieurs threads simultanément.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top