2008-12-03 22 views
14

Aquí está el escenario:Evitar una condición de carrera de javascript

A mis usuarios se les presenta una grilla, básicamente, una versión simplificada de una hoja de cálculo. Hay cuadros de texto en cada fila de la grilla. Cuando cambian un valor en un cuadro de texto, estoy validando su entrada, actualizando la colección que está manejando la cuadrícula y redibujando los subtotales en la página. Todo esto se maneja mediante el evento OnChange de cada cuadro de texto.

Cuando hacen clic en el botón "Guardar", estoy utilizando el evento OnClick del botón para realizar una validación final de los importes y luego enviar su entrada completa a un servicio web, guardándola.

Al menos, eso es lo que ocurre si tabulan en el formulario el botón Enviar.

El problema es que, si introducen un valor, inmediatamente hacen clic en el botón Guardar, SaveForm() comienza a ejecutarse antes de que UserInputChanged() complete - una condición de carrera. Mi código no utiliza setTimeout, pero yo estoy usando para simular el código de validación UserInputChanged lenta:

<!-- 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 --> 

No creo que pueda acelerar el código de validación - que es bastante ligero, pero al parecer, lento suficiente como para que el código intente llamar al servicio web antes de que se complete la validación.

En mi máquina, ~ 95ms es el número mágico entre si el código de validación se ejecuta antes de que comience el código de guardado. Esto puede ser mayor o menor dependiendo de la velocidad de la computadora del usuario.

¿Alguien tiene alguna idea de cómo manejar esta condición? Un compañero de trabajo sugirió usar un semáforo mientras el código de validación se está ejecutando y un bucle ocupado en el código de guardado para esperar hasta que el semáforo se desbloquee, pero me gustaría evitar el uso de cualquier tipo de bucle ocupado en mi código.

Respuesta

18

Usa el semáforo (llamémoslo StillNeedsValidating). si la función SaveForm ve que el semáforo StillNeedsValidating está activo, haga que active un segundo semáforo propio (que llamaré aquí FormNeedsSaving) y devuelva. Cuando la función de validación finaliza, si el semáforo de FormNeedsSaving está activo, llama a la función SaveForm por sí misma.

En 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; 
} 
+0

Exactamente lo que necesito. ¡Gracias! –

4

creo que el tiempo de espera está causando el problema ... si eso va a ser un código normal (sin AJAX asíncrona llama, los tiempos de espera, etc.), entonces no creo que SaveForm se ejecutará antes de UserInputChanged completa.

+0

Estoy de acuerdo. Quizás una mejor simulación de "código lento" sea simplemente tener un gran bucle "for" que no haga nada. De esta forma, no liberará el control del motor Javascript y debería asegurarse de que los eventos se manejen en el orden correcto. –

+0

No hay setTimeouts en el código actual. Hay una serie de aburridos getElementByIds, un isNan, un parseInt y una verificación para asegurarse de que la suma de todos los cuadros de texto no exceda una cantidad predefinida, pero nada asincrónico. –

7

Desactive el botón Guardar durante la validación. Configúrelo en deshabilitado como lo primero que hace la validación, y vuelva a habilitarlo cuando finalice.

p. Ej.

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

y

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

Vas a tener que ajustar cuando se quita el setTimeout y hacer la validación de una función, pero a menos que sus usuarios tienen reflejos sobrehumanos, que debe ser bueno para ir.

0

Puede configurar una función periódica que supervise el estado de toda la grilla y genere un evento que indique si toda la grilla es válida o no.

Su botón 'enviar formulario' se activará o deshabilitará en función de ese estado.

Oh veo una respuesta similar ahora - eso también funciona, por supuesto.

1

Un semáforo o mutex es probablemente el mejor camino a seguir, pero en lugar de un bucle ocupado, simplemente use un setTimeout() para simular un hilo inactivo. De esta manera:

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

Usted no tiene una condición de carrera, las condiciones de carrera no puede suceder en javascript desde javascript es de un solo subproceso, por lo 2 hilos no pueden estar interfiriendo entre sí.

El ejemplo que das no es un buen ejemplo. La llamada a setTimeout colocará la función llamada en una cola en el motor de JavaScript y la ejecutará más tarde. Si en ese momento hace clic en el botón Guardar, la función setTimeout no se invocará hasta DESPUÉS de que el guardado haya finalizado por completo.

Lo que probablemente esté sucediendo en su javascript es que el motor javascript llama al evento onClick antes de llamar al evento onChange.

Como sugerencia, tenga en cuenta que javascript tiene un único subproceso, a menos que utilice un depurador de javascript (firebug, microsoft screipt depurador). Esos programas interceptan el hilo y lo detienen. A partir de ese punto, se pueden ejecutar otros subprocesos (ya sea a través de eventos, llamadas setTimeout o manejadores XMLHttp), lo que hace parecer que javascript puede ejecutar varios subprocesos al mismo tiempo.

+8

Por supuesto, puede tener condiciones de carrera en Javascript, ¡es muy asíncrono! Solo hay un subproceso de interfaz de usuario, pero cualquier etiqueta

Cuestiones relacionadas