Comment forcer L'exécution JavaScript séquentielle?

J'ai seulement trouvé des réponses plutôt compliquées impliquant des classes, des gestionnaires d'événements et des rappels (qui me semblent être une approche un peu marteau). Je pense que les rappels peuvent être utiles mais je ne peux pas les appliquer dans le contexte le plus simple. Voir cet exemple:

<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 cela, la deuxième fonction se termine avant la première fonction; Quel est le moyen le plus simple (ou y en a-t-il un?) pour forcer la seconde fonction à retarder l'exécution jusqu'à ce que la première complète?

- - - Modifier - - -

Donc c'était un exemple d'ordures mais grâce à David Hedlund je vois avec ce nouvel exemple qu'il est en effet synchrone (avec le plantage de mon navigateur dans le processus de test!):

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

Comme mon problème réel était avec jQuery et IE, je devrai poster une question distincte à ce sujet si Je ne peux pas aller nulle part moi-même!

57
demandé sur ROMANIA_engineer 2009-12-07 13:36:49

10 réponses

Eh bien, setTimeout, selon sa définition, ne tiendra pas le fil. C'est souhaitable, car si c'était le cas, cela gèlerait toute l'interface utilisateur pendant le temps qu'elle attendait. si vous avez vraiment besoin d'utiliser setTimeout, alors vous devriez utiliser des fonctions de rappel:

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 vous êtes Pas en utilisant setTimeout, mais que vous avez juste des fonctions qui s'exécutent très longtemps, et que vous utilisiez setTimeout pour simuler cela, alors vos fonctions seraient réellement synchrones, et vous n'auriez pas ce problème à tout. Il convient de noter, cependant, que les requêtes AJAX sont asynchrones et, tout comme setTimeout, ne retiendront pas le thread de L'interface utilisateur jusqu'à ce qu'il soit terminé. Avec AJAX, comme avec setTimeout, vous devrez travailler avec des rappels.

43
répondu David Hedlund 2009-12-07 10:43:58

Je suis de retour à ces questions après tout ce temps parce qu'il m'a fallu tant de temps pour trouver ce que je pense être une solution propre : La seule façon de forcer une exécution séquentielle javascript que je connais est d'utiliser des promesses. Il y a des explications exhaustives des promesses à: Promises / A et Promises / a+

La seule bibliothèque implémentant des promesses que je connais est jquery, alors voici comment je résoudrais la question en utilisant des promesses 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>

En mettant en œuvre une promesse et enchaînant les fonctions .then() vous vous assurez que la deuxième fonction ne sera exécutée qu'après l'exécution de la première C'est la commande D. resolve() dans longfunctionfirst() qui donne le signal pour démarrer la fonction suivante.

Techniquement le shortfunctionsecond() n'a pas besoin de créer un différé et de retourner une promesse, mais je suis tombé amoureux des promesses et j'ai tendance à tout mettre en œuvre avec des promesses, désolé.

27
répondu Raymond 2013-08-20 12:23:26

Je suis une vieille main à la programmation et je suis revenu récemment à mon ancienne passion et j'ai du mal à m'intégrer dans ce nouveau monde lumineux orienté objet et événementiel et alors que je vois les avantages du comportement non séquentiel de Javascript, il y a du temps où cela gêne vraiment la simplicité et la réutilisabilité. Un exemple simple sur lequel j'ai travaillé était de prendre une photo (téléphone mobile programmé en javascript, HTML, phonegap,...), redimensionner et télécharger sur un site web. La séquence idéale est :

  1. Prendre une photo
  2. charge la photo dans un élément img
  3. redimensionner L'image (en utilisant Pixastic)
  4. Téléchargez-le sur un site web
  5. informer l'utilisateur sur l'échec de la réussite

Tout cela serait un programme séquentiel très simple si nous avions chaque étape renvoyant le contrôle à la suivante quand elle est terminée, mais en réalité:

  1. Prendre une photo est asynchrone, donc le programme tente de la charger dans l'élément img avant qu'elle n'existe
  2. charger le la photo est asynchrone, donc le redimensionnement de l'image commence avant que l'img soit complètement chargé
  3. le redimensionnement est asynchrone, donc le téléchargement sur le site web commence avant que l'image ne soit complètement redimensionnée
  4. le téléchargement sur le site web est asyn afin que le programme continue avant que la photo ne soit complètement téléchargée.

Et btw 4 des 5 étapes impliquent des fonctions de rappel.

Ma solution est donc d'imbriquer chaque étape dans la précédente et d'utiliser .onload et d'autres stratagèmes similaires, cela ressemble à ceci :

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

(j'espère que je n'ai pas fait trop d'erreurs en apportant le code à c'est essentiel la vraie chose est juste trop distrayante)

C'est je crois un exemple parfait où l'async n'est pas bon et la synchronisation est bonne, car contrairement à la gestion des événements de L'interface utilisateur, nous devons avoir chaque étape terminée avant l'exécution de la suivante, mais le code est une construction de poupée russe, il est déroutant et illisible, la réutilisabilité du code est difficile à apportez à la fonction interne tous les paramètres nécessaires sans les transmettre à chaque conteneur à son tour ou en utilisant des variables globales maléfiques, et j'aurais aimé que le résultat de tout ce code me donne un code de retour, mais le premier conteneur sera terminé bien avant que le code de retour ne soit disponible.

Maintenant, pour revenir à la question initiale de Tom, quelle serait la solution intelligente, facile à lire, facile à réutiliser à ce qui aurait été un programme très simple il y a 15 ans en utilisant disons C et une stupide carte électronique ?

L'exigence est en fait si simple que j'ai l'impression que je dois manquer une compréhension fondamentale de Javsascript et de la programmation moderne, sûrement la technologie est destinée à alimenter la productivité non ?.

Merci pour votre patience

Raymond le dinosaure ; -)

11
répondu Raymond 2010-10-22 04:37:34

En javascript, il n'y a aucun moyen de faire attendre le code. J'ai eu ce problème et la façon dont je l'ai fait était de faire un appel SJAX synchrone au serveur, et le serveur exécute réellement le sommeil ou fait une activité avant de revenir et tout le temps, le js attend.

Par exemple de SYNC AJAX: http://www.hunlock.com/blogs/Snippets:_Synchronous_AJAX

1
répondu rampr 2009-12-07 10:42:38

Dans votre exemple, la première fonction se termine réellement avant le démarrage de la deuxième fonction. setTimeout ne maintient pas l'exécution de la fonction jusqu'à ce que le délai d'attente soit atteint, il va simplement démarrer une minuterie en arrière-plan et exécuter votre déclaration d'alerte après l'heure spécifiée.

Il N'y a pas de façon native de faire un "sommeil" en JavaScript. Vous pouvez écrire une boucle qui vérifie le temps, mais cela mettra beaucoup de pression sur le client. Vous pouvez également faire L'AJAX synchrone appelez, comme emacsian décrit, mais cela mettra une charge supplémentaire sur votre serveur. Votre meilleur pari est vraiment d'éviter cela, ce qui devrait être assez simple pour la plupart des cas une fois que vous comprenez comment fonctionne setTimeout.

1
répondu Franz 2009-12-07 10:50:38

J'ai essayé la méthode de rappel et je n'ai pas pu faire fonctionner cela, ce que vous devez comprendre, c'est que les valeurs sont toujours atomiques même si l'exécution ne l'est pas. Par exemple:

alert('1');

alert('2');

Mais faire comme ça nous forcera à connaître l'ordre d'exécution:

loop=2;
total=0;
for(i=0;i<loop;i++) {
           total+=1;
           if(total == loop)
                      alert('2');
           else
                      alert('1');
}
1
répondu gamadril 2011-05-22 07:22:46

J'ai eu le même problème, c'est ma solution:

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
répondu Gyergyói Dávid 2016-06-01 14:59:04

Si vous n'insistez pas sur l'utilisation de Javascript, vous pouvez créer un code séquentiel dans Livescript et semble assez bon. Vous pouvez jeter un oeil à cet exemple :

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

Sortie:

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
répondu ceremcem 2016-04-22 12:29:36

Une autre façon de regarder cela est de passer en chaîne d'une fonction à l'autre. Avoir un tableau de fonctions qui est global à toutes vos fonctions appelées, disons:

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

Ensuite, configurez un tableau d'entiers sur les 'f" particuliers que vous voulez exécuter, par exemple

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

Ensuite, appelez une fonction initiale avec 'runorder' comme paramètre, par exemple f_start (runorder);

Ensuite, à la fin de chaque fonction, il suffit de faire apparaître l'index sur le 'F' suivant pour exécuter le tableau runorder et l'exécuter, toujours passer 'runorder' en paramètre mais avec le tableau réduit d'un.

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

Évidemment, cela se termine dans une fonction, disons à l'index 0, qui ne s'enchaîne pas sur une autre fonction. C'est complètement déterministe, en évitant les "minuteries".

0
répondu Allan 2016-08-13 13:38:59

Mettez votre code dans une chaîne, iterate, eval, setTimeout et recursion pour continuer avec les lignes restantes. Sans doute que je vais affiner cela ou tout simplement le jeter si elle ne touche pas la marque. Mon intention est de l'utiliser pour simuler des tests utilisateur vraiment, vraiment basiques.

La récursion et setTimeout le rendent séquentiel.

Pensées?

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

Sortie

STARTED READING LINES
123
124
1 p is 0
undefined
waited
p is 5
125
Did i finish?
COMPLETED READING LINES
0
répondu Jamie Nicholson 2018-05-10 06:12:02