2010-02-12 15 views
33

Tengo una función llamada save(), esta función reúne todas las entradas en la página y realiza una llamada AJAX al servidor para guardar el estado del trabajo del usuario.Seguridad de subprocesos en Javascript?

save() se llama actualmente cuando un usuario hace clic en el botón Guardar o realiza alguna otra acción que requiere que tengamos el estado más actual en el servidor (por ejemplo, generar un documento desde la página).

Estoy agregando la capacidad de guardar automáticamente el trabajo del usuario de vez en cuando. Primero, me gustaría evitar que se ejecuten AutoSave y un guardado generado por el usuario al mismo tiempo. Así tenemos el siguiente código (estoy cortando la mayor parte del código y esto no es una relación 1: 1, pero debería ser suficiente para transmitir la idea de):

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); 
} 

mi pregunta es si este código es seguro? ¿Los principales navegadores permiten que se ejecute solo un hilo a la vez?

Lo único que pensé es que sería peor para el usuario hacer clic en guardar y no obtener respuesta porque estamos guardando automáticamente (y sé cómo modificar el código para manejar esto). ¿Alguien ve otros problemas aquí?

+0

Así que basado en que si el usuario hace clic ahorrar el temporizador no dispararía hasta que termine la función de guardar? – JoshBerke

+1

Si te entiendo correctamente, estás preguntando si el usuario ha iniciado un guardado, el setTimeout iniciado save save alguna vez ingrese el mismo método. La respuesta es no, no estarán en el método save() al mismo tiempo, nunca. –

+0

¿Qué hacemos ya que esta información está un poco desactualizada? JavaScript ahora tiene varios hilos: https://developer.mozilla.org/En/Using_web_workers – jmort253

Respuesta

42

JavaScript en los navegadores es de un solo hilo. Solo estarás en una función en cualquier momento. Las funciones se completarán antes de que se ingrese la siguiente. Puede contar con este comportamiento, por lo tanto, si está en su función save(), nunca volverá a ingresarla hasta que la actual haya finalizado.

Cuando esto a veces se vuelve confuso (y sigue siendo cierto) es cuando tiene solicitudes de servidor asincrónicas (o setTimeouts o setIntervals), porque entonces parece que sus funciones son interleaved. Ellos no están.

En su caso, si bien las dos llamadas save() no se superpondrán entre sí, su guardado automático y el guardado del usuario pueden ocurrir seguidos.

Si solo quiere que ocurra un guardado al menos cada x segundos, puede hacer un setInterval en su función de guardar y olvidarse de ello. No veo la necesidad de la bandera isSaving.

creo que el código podría simplificarse mucho:

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

Sí, y dado que tengo llamadas asíncronas en la función de guardar usando el indicador de ahorro, debería permitirme evitar que alguien más entre en la función hasta que se haya ejecutado mi guardado final ... ¿verdad? – JoshBerke

+1

@Josh He actualizado mi respuesta un poco con respecto a la bandera isSaving. Todo lo que hace setTimeout es programar una llamada de función al menos unos x milisegundos en el futuro. No significa que abandonará lo que está haciendo y ejecutará su función: su guardado programado no se ejecutará al mismo tiempo que otra función. –

+0

Como save hace una llamada de asincronización a nuestro servidor, el isSaving evita que ocurra un guardado hasta que se llame a la función EndSave. – JoshBerke

2

Todos los principales navegadores actualmente de un solo hilo de ejecución de JavaScript (! Simplemente no lo utiliza web workers desde hace algunos navegadores son compatibles con esta técnica), por lo que este el enfoque es seguro.

Para un grupo de referencias, vea Is JavaScript Multithreaded?

+0

+1 para enlaces que incluyen a los trabajadores de la web. –

+0

¿Cuán compatibles están los trabajadores de la Web? Parece que Firefox 3.5+ no ha encontrado una lista que muestre el soporte del navegador para esta función. – JoshBerke

+0

No estoy del todo seguro. De acuerdo con John Resig (aquí - http://ejohn.org/blog/web-workers/), FireFox 3.5 y Safari 4 (desde el 29 de julio de 2009). Él también defiende la estructuración de su código para que usen trabajadores web cuando son compatibles (si lo leí bien). Además, parecen ser parte de la especificación HTML 5 (http://www.whatwg.org/specs/web-workers/current-work/) –

7

Todos los navegadores principales solo admiten un hilo de JavaScript (a menos que use web workers) en una página.

Sin embargo, las solicitudes XHR pueden ser asincrónicas. Pero mientras deshabilite la capacidad de guardar hasta que la solicitud actual para guardar devoluciones, todo salga bien.

Mi única sugerencia, es asegurarse de indicar al usuario de alguna manera cuando se produce un autoguardado (desactivar el botón guardar, etc.).

+0

ah, me pegaste al comentario de los webworkers. –

+0

Siéntete pensando en formas de indicar esto al usuario great point – JoshBerke

+0

Si tienes una cuenta de gmail, notarás que su enfoque cuando guardas automáticamente un borrador es para atenuar el botón "Guardar ahora" y cambiar el texto a "Guardar" y luego a "Guardado". Solo cuando el usuario comienza a realizar cambios en el borrador, el botón "Guardar ahora" vuelve a un estado en el que se puede hacer clic. – mikefrey

2

Parece seguro para mí.Javascript es el único subproceso (a menos que esté utilizando webworkers)

No es bastante el tema, pero esta entrada por John Resig cubre Javascript roscado y temporizadores: http://ejohn.org/blog/how-javascript-timers-work/

1

Creo que la forma en que está manejando es mejor para tu situación. Al usar la bandera, está garantizando que las llamadas asíncronas no se solapen. Tuve que lidiar también con llamadas asincrónicas al servidor y también utilicé algún tipo de indicador para evitar la superposición.

Como otros ya han señalado, JavaScript tiene un solo subproceso, pero las llamadas asincrónicas pueden ser complicadas si espera que las cosas digan lo mismo o que no sucedan durante el viaje de ida y vuelta al servidor.

Una cosa, sin embargo, es que no creo que realmente necesite desactivar el guardado automático. Si el guardado automático intenta ocurrir cuando un usuario está guardando, entonces el método de guardar simplemente regresará y no pasará nada. Por otro lado, deshabilita y vuelve a activar innecesariamente el autoguardado cada vez que se activa el guardado automático. Recomiendo cambiar a setInterval y luego olvidarlo.

Además, soy un riguroso para minimizar las variables globales. Me gustaría probablemente refactorizar el código de la siguiente manera:

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); 
    } 
    }; 
})(); 

Usted no tiene que refactorearlo así, por supuesto, pero como he dicho, me gustaría minimizar globales. Lo importante es que todo funcione sin desactivar y volver a habilitar el guardado automático cada vez que guarde.

Editar: Olvidó tuvo que crear una función de ahorro privado para poder hacer referencia a enableAutoSave

Cuestiones relacionadas