programacion - Cómo forzar la ejecución secuencial de Javascript?
lista de etiquetas javascript (10)
Solo he encontrado respuestas bastante complicadas que involucran clases, manejadores de eventos y devoluciones de llamadas (lo que me parece que es un enfoque un tanto macabro). Creo que las devoluciones de llamada pueden ser útiles, pero no puedo aplicarlas en el contexto más simple. Mira este ejemplo:
<html>
<head>
<script type="text/javascript">
function myfunction() {
longfunctionfirst();
shortfunctionsecond();
}
function longfunctionfirst() {
setTimeout(''alert("first function finished");'',3000);
}
function shortfunctionsecond() {
setTimeout(''alert("second function finished");'',200);
}
</script>
</head>
<body>
<a href="#" onclick="javascript:myfunction();return false;">Call my function</a>
</body>
</html>
En esto, la segunda función se completa antes de la primera función; ¿Cuál es la forma más simple (¿o hay una?) para forzar a la segunda función a retrasar la ejecución hasta que se complete la primera función.
---Editar---
Así que ese fue un ejemplo de basura, pero gracias a David Hedlund veo con este nuevo ejemplo que, de hecho, es sincrónico (¡junto con bloquear mi navegador en el proceso de prueba!):
<html>
<head>
<script type="text/javascript">
function myfunction() {
longfunctionfirst();
shortfunctionsecond();
}
function longfunctionfirst() {
var j = 10000;
for (var i=0; i<j; i++) {
document.body.innerHTML += i;
}
alert("first function finished");
}
function shortfunctionsecond() {
var j = 10;
for (var i=0; i<j; i++) {
document.body.innerHTML += i;
}
alert("second function finished");
}
</script>
</head>
<body>
<a href="#" onclick="javascript:myfunction();return false;">Call my function</a>
</body>
</html>
Como mi problema REAL fue con jQuery y IE, tendré que publicar una pregunta aparte sobre eso si no puedo llegar a ningún lado.
Bueno, setTimeout
, según su definición, no mantendrá el hilo. Esto es deseable, porque si lo hiciera, congelaría toda la IU durante el tiempo que estaba esperando. si realmente necesita usar setTimeout
, entonces debería usar funciones de devolución de llamada:
function myfunction() {
longfunctionfirst(shortfunctionsecond);
}
function longfunctionfirst(callback) {
setTimeout(function() {
alert(''first function finished'');
if(typeof callback == ''function'')
callback();
}, 3000);
};
function shortfunctionsecond() {
setTimeout(''alert("second function finished");'', 200);
};
Si no está utilizando setTimeout
, pero solo tiene funciones que se ejecutan durante mucho tiempo, y estaba usando setTimeout
para simular eso, entonces sus funciones serían realmente sincrónicas, y no tendría este problema en absoluto. Sin embargo, cabe señalar que las solicitudes AJAX son asíncronas y, al igual que setTimeout
, no mantendrán el hilo de la interfaz de usuario hasta que haya finalizado. Con AJAX, como con setTimeout
, tendrás que trabajar con devoluciones de llamadas.
Coloque su código en una cadena, itere, eval, setTimeout y recursión para continuar con las líneas restantes. Sin duda lo refinaré o simplemente lo tiraré si no da en el blanco. Mi intención es usarlo para simular pruebas de usuario realmente básicas.
La recursión y setTimeout lo hacen secuencial.
¿Pensamientos?
var line_pos = 0;
var string =`
console.log(''123'');
console.log(''line pos is ''+ line_pos);
SLEEP
console.log(''waited'');
console.log(''line pos is ''+ line_pos);
SLEEP
SLEEP
console.log(''Did i finish?'');
`;
var lines = string.split("/n");
var r = function(line_pos){
for (i = p; i < lines.length; i++) {
if(lines[i] == ''SLEEP''){
setTimeout(function(){r(line_pos+1)},1500);
return;
}
eval (lines[line_pos]);
}
console.log(''COMPLETED READING LINES'');
return;
}
console.log(''STARTED READING LINES'');
r.call(this,line_pos);
SALIDA
STARTED READING LINES
123
124
1 p is 0
undefined
waited
p is 5
125
Did i finish?
COMPLETED READING LINES
En javascript, no hay forma de que el código espere. He tenido este problema y la forma en que lo hice fue realizar una llamada SJAX sincrónica al servidor, y el servidor realmente ejecuta el modo de suspensión o hace alguna actividad antes de regresar y todo el tiempo, el js espera.
Por ejemplo, Sync AJAX: http://www.hunlock.com/blogs/Snippets:_Synchronous_AJAX
En su ejemplo, la primera función realmente se completa antes de que se inicie la segunda función. setTimeout no mantiene la ejecución de la función hasta que se alcanza el tiempo de espera, simplemente iniciará un temporizador en el fondo y ejecutará su declaración de alerta después del tiempo especificado.
No hay una forma nativa de hacer un "sueño" en JavaScript. Podría escribir un ciclo que verifique el tiempo, pero eso ejercerá mucha presión sobre el cliente. También podría hacer la llamada AJAX sincrónica, como se describió en emacsian, pero eso generará una carga extra en su servidor. Su mejor opción es realmente evitar esto, que debería ser lo suficientemente simple para la mayoría de los casos una vez que comprenda cómo funciona setTimeout.
Otra forma de ver esto es conectar en cadena de una función a otra. Disponga de una serie de funciones que son globales para todas sus funciones llamadas, por ejemplo:
arrf: [ f_final
,f
,another_f
,f_again ],
A continuación, configure una matriz de enteros para las ''f'' particulares que desea ejecutar, por ejemplo
var runorder = [1,3,2,0];
Luego llame a una función inicial con ''runorder'' como parámetro, por ej. F_start (runorder);
Luego, al final de cada función, simplemente pase el índice a la siguiente ''f'' para ejecutar la matriz runorder y ejecútela, aún pasando ''runorder'' como parámetro pero con la matriz reducida en uno.
var nextf = runorder.shift();
arrf[nextf].call(runorder);
Obviamente, esto termina en una función, digamos en el índice 0, que no se encadena a otra función. Esto es completamente determinista, evitando ''temporizadores''.
Probé la forma de devolución de llamada y no pude hacer que esto funcionara, lo que tienes que entender es que los valores todavía son atómicos aunque la ejecución no lo sea. Por ejemplo:
alert(''1'');
<--- estas dos funciones se ejecutarán al mismo tiempo
alert(''2'');
<--- estas dos funciones se ejecutarán al mismo tiempo
pero hacer esto nos obligará a conocer el orden de ejecución:
loop=2;
total=0;
for(i=0;i<loop;i++) {
total+=1;
if(total == loop)
alert(''2'');
else
alert(''1'');
}
Si no insiste en usar Javascript puro, puede crear un código secuencial en Livescript y se ve bastante bien . Es posible que desee echar un vistazo a este ejemplo :
# application
do
i = 3
console.log td!, "start"
<- :lo(op) ->
console.log td!, "hi #{i}"
i--
<- wait-for /something
if i is 0
return op! # break
lo(op)
<- sleep 1500ms
<- :lo(op) ->
console.log td!, "hello #{i}"
i++
if i is 3
return op! # break
<- sleep 1000ms
lo(op)
<- sleep 0
console.log td!, "heyy"
do
a = 8
<- :lo(op) ->
console.log td!, "this runs in parallel!", a
a--
go /something
if a is 0
return op! # break
<- sleep 500ms
lo(op)
Salida:
0ms : start
2ms : hi 3
3ms : this runs in parallel! 8
3ms : hi 2
505ms : this runs in parallel! 7
505ms : hi 1
1007ms : this runs in parallel! 6
1508ms : this runs in parallel! 5
2009ms : this runs in parallel! 4
2509ms : hello 0
2509ms : this runs in parallel! 3
3010ms : this runs in parallel! 2
3509ms : hello 1
3510ms : this runs in parallel! 1
4511ms : hello 2
4511ms : heyy
Soy un veterano en programación y volví recientemente a mi vieja pasión y estoy luchando para encajar en este nuevo mundo brillante orientado a Objetos, eventos y mientras veo las ventajas del comportamiento no secuencial de Javascript, hay tiempo en que realmente llega en el camino de la simplicidad y la reutilización. Un ejemplo simple en el que he trabajado fue tomar una foto (teléfono móvil programado en javascript, HTML, phonegap, ...), cambiar el tamaño y cargarlo en un sitio web. La secuencia ideal es:
- Toma una foto
- Cargue la foto en un elemento img
- Cambiar el tamaño de la imagen (usando Pixastic)
- Subirlo a un sitio web
- Informar al usuario sobre el fracaso del éxito
Todo esto sería un programa secuencial muy simple si tuviéramos que cada paso devolviera el control al siguiente cuando esté terminado, pero en realidad:
- Tomar una foto es asincrónico, por lo que el programa intenta cargarlo en el elemento img antes de que exista
- Cargar la foto es asíncrona para que la imagen cambie de tamaño antes de que el img esté completamente cargado
- El tamaño es asíncrono, así que suba al inicio del sitio web antes de que la imagen se modifique por completo
- Subir al sitio web es asyn para que el programa continúe antes de que la foto se cargue por completo.
Y, por cierto, 4 de los 5 pasos implican funciones de devolución de llamada.
Mi solución es anidar cada paso en el anterior y usar .onload y otras estratagemas similares. Se ve así:
takeAPhoto(takeaphotocallback(photo) {
photo.onload = function () {
resizePhoto(photo, resizePhotoCallback(photo) {
uploadPhoto(photo, uploadPhotoCallback(status) {
informUserOnOutcome();
});
});
};
loadPhoto(photo);
});
(Espero no haber cometido demasiados errores al traer el código, es esencial, lo real es demasiado molesto)
Creo que es un ejemplo perfecto en el que asincrónico no es bueno y la sincronización es buena, porque al contrario del manejo de eventos Ui debemos completar cada paso antes de que se ejecute el siguiente, pero el código es una construcción de muñeca rusa, es confuso e ilegible, la reutilización del código es difícil de lograr debido a todo el anidamiento, es simplemente difícil llevar a la función interna todos los parámetros necesarios sin pasarlos a cada contenedor por turno o usar variables globales malvadas, y me habría encantado que el resultado de todo este código me daría un código de retorno, pero el primer contenedor estará terminado mucho antes de que esté disponible el código de retorno.
Ahora, para volver a la pregunta inicial de Tom, ¿cuál sería la solución inteligente, fácil de leer, fácil de reutilizar de lo que hubiera sido un programa muy simple hace 15 años usando let C y una placa electrónica tonta?
El requisito es, de hecho, tan simple que tengo la impresión de que me falta un conocimiento fundamental de Javsascript y de la programación moderna. Sin duda, la tecnología está destinada a impulsar la productividad ¿verdad?
Gracias por su paciencia
Raymond el dinosaurio ;-)
Tuve el mismo problema, esta es mi solución:
var functionsToCall = new Array();
function f1() {
$.ajax({
type:"POST",
url: "/some/url",
success: function(data) {
doSomethingWith(data);
//When done, call the next function..
callAFunction("parameter");
}
});
}
function f2() {
/*...*/
callAFunction("parameter2");
}
function f3() {
/*...*/
callAFunction("parameter3");
}
function f4() {
/*...*/
callAFunction("parameter4");
}
function f5() {
/*...*/
callAFunction("parameter5");
}
function f6() {
/*...*/
callAFunction("parameter6");
}
function f7() {
/*...*/
callAFunction("parameter7");
}
function f8() {
/*...*/
callAFunction("parameter8");
}
function f9() {
/*...*/
callAFunction("parameter9");
}
function callAllFunctionsSy(params) {
functionsToCall.push(f1);
functionsToCall.push(f2);
functionsToCall.push(f3);
functionsToCall.push(f4);
functionsToCall.push(f5);
functionsToCall.push(f6);
functionsToCall.push(f7);
functionsToCall.push(f8);
functionsToCall.push(f9);
functionsToCall.reverse();
callAFunction(params);
}
function callAFunction(params) {
if (functionsToCall.length > 0) {
var f=functionsToCall.pop();
f(params);
}
}
Volví a estas preguntas después de todo este tiempo porque me tomó tanto tiempo encontrar lo que creo que es una solución limpia: la única forma de forzar una ejecución secuencial de javascript que conozco es usar promesas. Hay explicaciones exhaustivas de promesas en: Promises/A y Promises/A+
La única biblioteca que implementa promesas que sé es jquery, así que aquí es cómo resolvería la pregunta usando las promesas jquery:
<html>
<head>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
function myfunction()
{
promise = longfunctionfirst().then(shortfunctionsecond);
}
function longfunctionfirst()
{
d = new $.Deferred();
setTimeout(''alert("first function finished");d.resolve()'',3000);
return d.promise()
}
function shortfunctionsecond()
{
d = new $.Deferred();
setTimeout(''alert("second function finished");d.resolve()'',200);
return d.promise()
}
</script>
</head>
<body>
<a href="#" onclick="javascript:myfunction();return false;">Call my function</a>
</body>
</html>
Al implementar una promesa y encadenar las funciones con .then (), se asegura de que la segunda función se ejecutará solo después de que la primera se haya ejecutado. Es el comando d.resolve () en longfunctionfirst () que da la señal para iniciar la siguiente función.
Técnicamente el shortfunctionsecond () no necesita crear un diferido y devolver una promesa, pero me enamoré de las promesas y tento a implementar todo con promesas, lo siento.