2010-02-12 6 views
33

J'ai une fonction appelée save(), cette fonction rassemble toutes les entrées sur la page, et effectue un appel AJAX au serveur pour sauvegarder l'état du travail de l'utilisateur. Save() est actuellement appelé quand un utilisateur clique sur le bouton de sauvegarde, ou effectue une autre action qui nous oblige à avoir l'état le plus courant sur le serveur (générer un document à partir de la page par exemple).Fil Sécurité en Javascript?

J'ajoute à la capacité d'enregistrer automatiquement le travail de l'utilisateur de temps en temps. Tout d'abord, je voudrais empêcher une sauvegarde automatique et une sauvegarde générée par l'utilisateur de s'exécuter en même temps. Nous avons donc le code (je coupe le plus du code et ce n'est pas 1: 1, mais devrait être suffisant pour faire passer l'idée): après

var isSaving=false; 
var timeoutId; 
var timeoutInterval=300000; 
function save(showMsg) 
{ 
    //Don't save if we are already saving. 
    if (isSaving) 
    { 
    return; 
    } 
    isSaving=true; 
    //disables the autoSave timer so if we are saving via some other method 
    //we won't kick off the timer. 
    disableAutoSave(); 

    if (showMsg) { //show a saving popup} 
    params=CollectParams(); 
    PerformCallBack(params,endSave,endSaveError); 

} 
function endSave() 
{ 
    isSaving=false; 
    //hides popup if it's visible 

    //Turns auto saving back on so we save x milliseconds after the last save. 
    enableAutoSave(); 

} 
function endSaveError() 
{ 
    alert("Ooops"); 
    endSave(); 
} 
function enableAutoSave() 
{ 
    timeoutId=setTimeOut(function(){save(false);},timeoutInterval); 
} 
function disableAutoSave() 
{ 
    cancelTimeOut(timeoutId); 
} 

Ma question est de savoir si ce code est sûr? Est-ce que les principaux navigateurs n'autorisent qu'un seul thread à s'exécuter à la fois? Une pensée que j'ai eu, c'est qu'il serait pire pour l'utilisateur de cliquer sur Enregistrer et d'obtenir aucune réponse, car nous sommes en mode automatique (et je sais comment modifier le code pour gérer cela). Quelqu'un voit-il d'autres problèmes ici?

+0

basé sur De sorte que si l'utilisateur clique enregistrer la minuterie ne se déclenche pas jusqu'à ce que la fonction de sauvegarde est terminée? – JoshBerke

+1

Si je vous comprends bien, vous demandez si l'utilisateur a lancé une sauvegarde, le setTimeout initié enregistrer jamais entrer la même méthode. La réponse est non, ils ne seront pas dans la méthode save() en même temps, jamais. –

+0

Que faisons-nous puisque cette information est légèrement dépassée? JavaScript a maintenant plusieurs threads: https://developer.mozilla.org/En/Using_web_workers – jmort253

Répondre

42

JavaScript dans les navigateurs est seul fileté. Vous ne serez jamais dans une fonction à aucun moment dans le temps. Les fonctions se termineront avant que la suivante soit entrée. Vous pouvez compter sur ce comportement, donc si vous êtes dans votre fonction save(), vous ne l'entrerez jamais à nouveau tant que l'actuel n'est pas terminé. Où cela devient parfois confus (et reste vrai) lorsque vous avez des demandes de serveur asynchrones (ou setTimeouts ou setIntervals), car il semble que vos fonctions sont interleaved. Ils ne sont pas.

Dans votre cas, alors que deux appels save() ne se chevauchent pas l'un l'autre, votre sauvegarde automatique et l'enregistrement de l'utilisateur peuvent se produire l'un après l'autre.

Si vous voulez juste qu'une sauvegarde se produise au moins toutes les x secondes, vous pouvez faire un setInterval sur votre fonction de sauvegarde et l'oublier. Je ne vois pas le besoin du drapeau isSaving.

Je pense que votre code pourrait être beaucoup simplifié:

var intervalTime = 300000; 
var intervalId = setInterval("save('my message')", intervalTime); 
function save(showMsg) 
{ 
    if (showMsg) { //show a saving popup} 
    params=CollectParams(); 
    PerformCallBack(params, endSave, endSaveError); 

    // You could even reset your interval now that you know we just saved. 
    // Of course, you'll need to know it was a successful save. 
    // Doing this will prevent the user clicking save only to have another 
    // save bump them in the face right away because an interval comes up. 
    clearInterval(intervalId); 
    intervalId = setInterval("save('my message')", intervalTime); 
} 

function endSave() 
{ 
    // no need for this method 
    alert("I'm done saving!"); 
} 

function endSaveError() 
{ 
    alert("Ooops"); 
    endSave(); 
} 
+0

Oui et puisque j'ai des appels asynchrones dans la fonction de sauvegarde en utilisant le drapeau isSaving devrait me permettre d'empêcher quelqu'un d'entrer dans la fonction jusqu'à ce que ma sauvegarde de fin a couru ... non? – JoshBerke

+1

@Josh J'ai mis à jour ma réponse concernant le drapeau isSaving. Tout ce que setTimeout fait est de planifier un appel de fonction au moins quelques millisecondes dans le futur. Cela ne signifie pas qu'il va laisser tomber ce qu'il fait et exécuter votre fonction - votre sauvegarde programmée ne fonctionnera pas en même temps qu'une autre fonction. –

+0

Puisque save effectue un appel asynchrone à notre serveur, le isSaving empêche une sauvegarde de se produire jusqu'à ce que la fonction EndSave soit appelée. – JoshBerke

2

Tous les principaux navigateurs javascript exécution actuellement un seul fil (! Il suffit de ne pas utiliser web workers depuis quelques navigateurs supportent cette technique), cette l'approche est sûre.

Pour un ensemble de références, voir Is JavaScript Multithreaded?

+0

+1 pour les liens incluant les web workers. –

+0

Quelle est la prise en charge des travailleurs Web? On dirait que Firefox 3.5+ n'a pas trouvé de liste montrant le support du navigateur pour cette fonctionnalité. – JoshBerke

+0

Je ne suis pas entièrement sûr. Selon John Resig (ici - http://ejohn.org/blog/web-workers/), FireFox 3.5 et Safari 4 (au 29 juillet 2009). Il plaide aussi pour la structuration de votre code afin qu'ils utilisent des web workers quand ils sont supportés (si je lis bien). Qui plus est, ils semblent faire partie de la spécification HTML 5 (http://www.whatwg.org/specs/web-workers/current-work/) –

7

Tous les principaux navigateurs prennent en charge un seul thread javascript (sauf si vous utilisez web workers) sur une page.

Les demandes XHR peuvent être asynchrones, cependant. Mais tant que vous désactivez la possibilité d'enregistrer jusqu'à ce que la demande en cours pour enregistrer les retours, tout devrait bien fonctionner.

Ma seule suggestion est de vous assurer d'indiquer à l'utilisateur quand un enregistrement automatique a lieu (désactiver le bouton de sauvegarde, etc.).

+0

ah, vous me battre au commentaire des webwares. –

+0

Yep penser à des façons de l'indiquer au bon utilisateur – JoshBerke

+0

Si vous avez un compte Gmail, vous remarquerez que leur approche lors de l'enregistrement automatique d'un brouillon est de griser le bouton "Save Now" et de changer le texte en "Saving" et ensuite à "Saved". Ce n'est que lorsque l'utilisateur commence à modifier le brouillon que le bouton «Enregistrer maintenant» revient à un état cliquable. – mikefrey

1

Je pense que la façon dont vous le manipuler est le mieux pour ta situation. En utilisant le drapeau, vous garantissez que les appels asynchrones ne se chevauchent pas. J'ai également dû faire face à des appels asynchrones au serveur et utiliser une sorte de drapeau pour éviter les chevauchements. Comme d'autres l'ont déjà signalé, JavaScript est un thread unique, mais les appels asynchrones peuvent être difficiles si vous vous attendez à ce que les choses disent la même chose ou ne se produisent pas lors de l'aller-retour au serveur. Une chose, cependant, c'est que je ne pense pas que vous devez désactiver l'enregistrement automatique. Si l'enregistrement automatique tente de se produire lorsqu'un utilisateur est en train d'enregistrer, la méthode de sauvegarde retournera simplement et rien ne se passera. D'un autre côté, vous désactivez et réactivez inutilement l'enregistrement automatique à chaque fois que la sauvegarde automatique est activée. Je recommanderais de passer à setInterval et de l'oublier.

En outre, je suis un stickler pour minimiser les variables globales. Je serais probablement Refactor votre code comme ceci:

var saveWork = (function() { 
    var isSaving=false; 
    var timeoutId; 
    var timeoutInterval=300000; 
    function endSave() { 
     isSaving=false; 
     //hides popup if it's visible 
    } 
    function endSaveError() { 
    alert("Ooops"); 
    endSave(); 
    } 
    function _save(showMsg) { 
    //Don't save if we are already saving. 
    if (isSaving) 
    { 
    return; 
    } 
    isSaving=true; 

    if (showMsg) { //show a saving popup} 
    params=CollectParams(); 
    PerformCallBack(params,endSave,endSaveError); 
    } 
    return { 
    save: function(showMsg) { _save(showMsg); }, 
    enableAutoSave: function() { 
     timeoutId=setInterval(function(){_save(false);},timeoutInterval); 
    }, 
    disableAutoSave: function() { 
     cancelTimeOut(timeoutId); 
    } 
    }; 
})(); 

Vous n'avez pas à factoriser comme ça, bien sûr, mais comme je l'ai dit, je tiens à minimiser globals. L'important est que le tout fonctionne sans désactivation et réactivation automatique à chaque fois que vous enregistrez.

Edit: J'ai oublié a dû créer une fonction de sauvegarde privée pour être en mesure de faire référence à partir enableAutoSave

Questions connexes