web-dev-qa-db-fra.com

Comment forcer l'exécution Javascript séquentielle?

Je n'ai trouvé que des réponses assez compliquées impliquant des classes, des gestionnaires d'événements et des callbacks (ce qui me semble être une approche un peu féroce). Je pense que les rappels peuvent être utiles, mais je n'arrive 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 existe-t-il?) de forcer la deuxième fonction à retarder l'exécution jusqu'à la fin de la première fonction?

---Modifier---

C’était donc un exemple douteux, mais grâce à David Hedlund, je vois avec ce nouvel exemple qu’il est effectivement synchrone (avec le crash 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 ACTUEL était avec jQuery et IE, je vais devoir poser une question distincte à ce sujet si je ne peux aller nulle part moi-même!

60
Tom

setTimeout, selon sa définition, ne tiendra pas le fil. Cela est souhaitable, car si c'était le cas, il gèlerait toute l'interface utilisateur pendant le temps qu'il attendait. si vous avez vraiment besoin d'utiliser setTimeout, alors vous devriez utiliser les 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 utilisez pas en utilisant setTimeout, mais que vous avez simplement des fonctions qui s'exécutent très longtemps et que vous utilisez setTimeout pour simuler cela, vos fonctions seraient seraient en fait synchrones et vous n'auriez pas ce problème à tout. Toutefois, il convient de noter que les demandes AJAX sont asynchrones et, tout comme setTimeout, ne maintiendront pas le thread d'interface utilisateur tant qu'il ne sera pas terminé. Avec AJAX, comme avec setTimeout, vous devrez travailler avec des rappels.

44
David Hedlund

Je suis revenu à cette question après tout ce temps, car il m'a fallu beaucoup de temps pour trouver ce que je pense être une solution propre: Le seul moyen de forcer une exécution séquentielle javascript que je connaisse est d'utiliser des promesses . Des explications exhaustives des promesses sont disponibles sur: Promises/A et Promises/A +

JQuery est la seule bibliothèque qui mette en œuvre les promesses que je connaisse. Voici comment résoudre la question à l’aide de 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 implémentant une promesse et en chaînant les fonctions avec .then (), vous vous assurez que la seconde 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 à lancer la fonction suivante.

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

30
Raymond

Je suis un habitué de la programmation et je suis revenu récemment à mon ancienne passion. Je lutte pour m'inscrire dans ce nouveau monde lumineux orienté objet et événement. Bien que je voie les avantages du comportement non séquentiel de Javascript, il y a du temps où il faut vraiment En termes de simplicité et de possibilité de réutilisation ... Un exemple simple sur lequel j’ai travaillé est de prendre une photo (téléphone portable programmé en javascript, HTML, phonegap, ...), de la redimensionner et de la télécharger sur un site Web . La séquence idéale est:

  1. Prendre une photo
  2. Charger la photo dans un élément img
  3. Redimensionner l'image (Utilisation de Pixastic)
  4. Téléchargez-le sur un site Web
  5. Informer l'utilisateur en cas d'échec

Tout ceci constituerait un programme séquentiel très simple si chaque étape devait rendre le contrôle à la suivante une fois terminée, mais en réalité:

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

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

Ma solution consiste donc à imbriquer chaque étape de la précédente et à utiliser .onload et d’autres stratagèmes similaires. Elle se présente comme suit:

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, il est essentiel que la vraie chose soit trop distrayante)

C’est un exemple parfait où async n’est pas bon et que la synchronisation est bonne, car contrairement à la gestion des événements Ui, nous devons avoir terminé chaque étape avant d’exécuter la suivante, mais le code est une construction de poupée russe, elle est déroutante et illisible. la possibilité de réutilisation du code est difficile à réaliser car, du fait de toute imbrication, il est tout simplement difficile d’apporter à la fonction interne tous les paramètres nécessaires sans les transmettre à chaque conteneur ou d’utiliser des variables globales pervers, et j’aurais aimé que le résultat de tous ce code me donnerait 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 et à réutiliser pour ce qui aurait été un programme très simple il y a 15 ans, en utilisant let say C et un tableau électronique muet?

L’exigence est en fait si simple que j’ai l’impression que je dois rater une compréhension fondamentale de JavaScript et de la programmation moderne. La technologie est assurément destinée à favoriser la productivité, non?.

Merci pour votre patience

Raymond le dinosaure ;-)

11
Raymond

J'ai essayé la méthode de rappel et je n'ai pas réussi à 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'); <--- ces deux fonctions seront exécutées en même temps

alert('2'); <--- ces deux fonctions seront exécutées en même temps

mais cela nous obligera à 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
gamadril

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 est de faire un appel SJAX synchrone au serveur, et le serveur exécute le sommeil ou effectue une activité avant de revenir et pendant tout le temps, le js attend.

Exemple de synchronisation AJAX: http://www.hunlock.com/blogs/Snippets:_Synchronous_AJAX

1
rampr

Dans votre exemple, la première fonction est effectivement terminée avant le démarrage de la deuxième. setTimeout ne bloque pas l'exécution de la fonction jusqu'à ce que le délai soit atteint, il démarre simplement une minuterie en arrière-plan et exécute votre déclaration d'alerte après le délai spécifié.

Il n’existe pas de méthode native pour "dormir" en JavaScript. Vous pouvez écrire une boucle qui vérifie l'heure, mais cela mettra beaucoup de pression sur le client. Vous pouvez également faire l'appel synchrone AJAX, comme décrit par emacsian, mais cela surchargera votre serveur. Votre meilleur pari est vraiment d'éviter cela, ce qui devrait être assez simple dans la plupart des cas une fois que vous aurez compris comment fonctionne setTimeout.

1
Franz

J'ai eu le même problème, voici 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
Gyergyói Dávid

Placez votre code dans une chaîne, iterate, eval, setTimeout et récursivité pour continuer avec les lignes restantes. Nul doute que je vais affiner ceci ou juste le jeter si ça ne touche pas la cible. Mon intention est de l'utiliser pour simuler des tests utilisateur vraiment basiques.

La récursivité 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
Jamie Nicholson

Une autre façon de voir les choses est de connecter en chaîne une fonction à une autre . Avoir un tableau de fonctions global à toutes vos fonctions appelées, par exemple:

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

Puis configurez un tableau d’entiers correspondant aux "f" que vous voulez exécuter, par exemple

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

Appelez ensuite une fonction initiale avec 'runorder' comme paramètre, par exemple . f_start (runorder);

Ensuite, à la fin de chaque fonction, ajoutez simplement l'index au prochain "f" pour l'exécuter hors du tableau de runorder et exécutez-le, en passant toujours "runorder" en paramètre, mais avec le tableau réduit de un.

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

Évidemment, cela se termine par une fonction, par exemple à l'indice 0, qui ne se lie pas à une autre fonction ..__ Ceci est complètement déterministe, évitant les "timers".

0
Allan

Si vous n'insistez pas pour utiliser du Javascript pur, vous pouvez créer un code séquentiel dans Livescript et il est plutôt joli. Vous voudrez peut-être jeter un oeil sur 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
ceremcem