¿Cómo forzar la Ejecución Secuencial de Javascript?


Solo he encontrado respuestas bastante complicadas que involucran clases, controladores de eventos y devoluciones de llamada (que me parecen un enfoque un tanto mazo). Creo que las devoluciones de llamada pueden ser útiles, pero parece que no puedo aplicarlas en el contexto más simple. Ver 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 la segunda función para retrasar la ejecución hasta que la primera función es ¿completa?

---Edit---

Por lo que fue un ejemplo basura, pero gracias a David Hedlund veo con este nuevo ejemplo que es de hecho sincrónico (junto con estrellarse 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 e IE, tendré que publicar una pregunta separada sobre eso si no puedo llegar a ninguna parte.

Author: ROMANIA_engineer, 2009-12-07

10 answers

Bueno, setTimeout, por su definición, no sostendrá el hilo. Esto es deseable, porque si lo hiciera, congelaría toda la interfaz de usuario 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á usando, pero solo tiene funciones que se ejecutan durante mucho tiempo, y usa setTimeout para simular eso, entonces sus funciones serían en realidad sincrónicas, y no tendría este problema en todo. Debe tenerse en cuenta, sin embargo, que las solicitudes AJAX son asíncronas, y, al igual que setTimeout, no sostendrán el subproceso de la interfaz de usuario hasta que haya terminado. Con AJAX, al igual que con setTimeout, tendrás que trabajar con callbacks.

 43
Author: David Hedlund,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-07 10:43:58

Estoy de vuelta a estas preguntas después de todo este tiempo porque me llevó tanto tiempo encontrar lo que creo que es una solución limpia : La única manera de forzar una ejecución secuencial de javascript que conozco es usar promesas. Hay explicaciones exhaustivas de las promesas en: Promises/A y Promises/A +

La única biblioteca que implementa promesas que conozco es jquery, así que aquí está cómo resolvería la pregunta usando jquery promises:

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

Aplicando una promesa y encadenar las funciones con .entonces () 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 () el 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 tiendo a implementar todo con promesas, lo siento.

 27
Author: Raymond,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-20 12:23:26

Soy un viejo experto en programación y volví recientemente a mi antigua pasión y estoy luchando para encajar en este nuevo mundo brillante orientado a objetos, impulsado por eventos y mientras veo las ventajas del comportamiento no secuencial de Javascript, hay momentos en los que realmente se interpone 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,...), redimensionarlo y subirlo a un sitio web. La secuencia ideal es :

  1. Tome una foto
  2. Cargar la foto en un elemento img
  3. Cambiar el tamaño de la imagen (Usando Pixastic)
  4. Subirlo a un sitio web
  5. Informar al usuario en caso de fallo de éxito

Todo esto sería un programa secuencial muy simple si tuviéramos cada paso devolviendo el control al siguiente cuando esté terminado, pero en realidad:

  1. Tomar una foto es asincrónica, por lo que el programa intenta cargarla en el elemento img antes de que exista
  2. Cargar el la foto es asíncrona, por lo que el cambio de tamaño de la imagen comienza antes de que el img esté completamente cargado
  3. Redimensionar es asincrónico, por lo que la carga en el sitio web comienza antes de que la Imagen se redimensione completamente
  4. La carga en el sitio web es asyn, por lo que el programa continúa antes de que la foto se cargue por completo.

Y por cierto 4 de los 5 pasos implican funciones de devolución de llamada.

Por lo tanto, mi solución es anidar cada paso en el anterior y usarlo .onload y otras estratagemas similares, se ve algo como esto :

takeAPhoto(takeaphotocallback(photo) {
  photo.onload = function () {
    resizePhoto(photo, resizePhotoCallback(photo) {
      uploadPhoto(photo, uploadPhotoCallback(status) {
        informUserOnOutcome();
      });
    }); 
  };
  loadPhoto(photo);
});

(Espero no haber cometido demasiados errores al llevar el código a lo esencial, lo real es demasiado distractor)

Este es creo que un ejemplo perfecto donde la async no es buena y la sincronización es buena, porque contrariamente al manejo de eventos de interfaz de usuario debemos tener cada paso terminado 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 traiga a la función interna todos los parámetros necesarios sin pasarlos a cada contenedor por turno o usando variables globales malvadas, y me hubiera encantado que el resultado de todo este código me diera un código de retorno, pero el primer contenedor se terminará mucho antes de que el código de retorno esté disponible.

Ahora, volviendo a la pregunta inicial de Tom, ¿cuál sería la solución inteligente, fácil de leer, fácil de reutilizar a lo que habría sido un programa muy simple hace 15 años usando digamos C y ¿un tablero electrónico tonto ?

El requisito es de hecho tan simple que tengo la impresión de que debo estar perdiendo una comprensión fundamental de Javsascript y la programación moderna, Seguramente la tecnología está destinada a alimentar la productividad ¿verdad ?.

Gracias por su paciencia

Raymond el Dinosaurio; -)

 11
Author: Raymond,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-22 04:37:34

En javascript, no hay manera de hacer que el código espere. He tenido este problema y la forma en que lo hice fue hacer una llamada SJAX síncrona al servidor, y el servidor realmente ejecuta sleep o hace alguna actividad antes de regresar y todo el tiempo, el js espera.

Por ejemplo, de Sincronización AJAX: http://www.hunlock.com/blogs/Snippets:_Synchronous_AJAX

 1
Author: rampr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-07 10:42:38

En su ejemplo, la primera función realmente se completa antes de que se inicie la segunda función. setTimeout no retiene la ejecución de la función hasta que se alcanza el tiempo de espera, simplemente iniciará un temporizador en segundo plano y ejecutará su instrucción alert después del tiempo especificado.

No hay una forma nativa de hacer un "sleep" en JavaScript. Podrías escribir un bucle que verifique el tiempo, pero eso pondrá mucha presión en el cliente. También puedes hacer el AJAX síncrono llame, como Emacsian describió, pero eso pondrá una carga extra en su servidor. Su mejor apuesta es realmente evitar esto, que debería ser lo suficientemente simple para la mayoría de los casos una vez que entienda cómo funciona setTimeout.

 1
Author: Franz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-07 10:50:38

Probé la forma de devolución de llamada y no pude hacer que esto funcione, lo que tienes que entender es que los valores siguen siendo atómicos aunque la ejecución no lo sea. Por ejemplo:

alert('1');

alert('2');

Pero hacer así 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');
}
 1
Author: gamadril,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-22 07:22:46

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);
	}
}
 1
Author: Gyergyói Dávid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-01 14:59:04

Si no insistes en usar Javascript puro, puedes construir 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
 0
Author: ceremcem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-22 12:29:36

Otra forma de ver esto es encadenar de una función a otra. Tener una matriz de funciones que es global a todas sus funciones llamadas, por ejemplo:

arrf: [ f_final
       ,f
       ,another_f
       ,f_again ],

Luego configure una matriz de enteros para la 'f" s particular que desea ejecutar, por ejemplo

var runorder = [1,3,2,0];

Luego llame a una función inicial con 'runorder' como parámetro, p.ej. f_start (orden de ejecución);

Luego, al final de cada función, simplemente coloque el índice en la siguiente 'f' para ejecutar el array runorder y ejecutarlo, aún pasando 'runorder' como parámetro pero con el array reducido en uno.

var nextf = runorder.shift();
arrf[nextf].call(runorder);

Obviamente esto termina en una función, digamos en el índice 0, que no encadena a otra función. Esto es completamente determinista, evitando 'temporizadores'.

 0
Author: Allan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-13 13:38:59

Ponga su código en una cadena, iterar, eval, setTimeout y recursión para continuar con las líneas restantes. Sin duda refinaré esto 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
 0
Author: Jamie Nicholson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-10 06:12:02