Jak mam wywołać 3 funkcje, aby wykonać je jedna po drugiej?


151

Jeśli potrzebuję wywołać te funkcje jedna po drugiej,

$('#art1').animate({'width':'1000px'},1000);        
$('#art2').animate({'width':'1000px'},1000);        
$('#art3').animate({'width':'1000px'},1000);        

Wiem, że w jQuery mógłbym zrobić coś takiego:

$('#art1').animate({'width':'1000px'},1000,'linear',function(){
    $('#art2').animate({'width':'1000px'},1000,'linear',function(){
        $('#art3').animate({'width':'1000px'},1000);        
    });        
});        

Ale załóżmy, że nie używam jQuery i chcę wywołać:

some_3secs_function(some_value);        
some_5secs_function(some_value);        
some_8secs_function(some_value);        

Jak powinienem wywołać te funkcje, aby wykonać some_3secs_function, a PO zakończeniu tego wywołania, wykonać some_5secs_functioni PO zakończeniu tego wywołania, a następnie wywołać some_8secs_function?

AKTUALIZACJA:

To nadal nie działa:

(function(callback){
    $('#art1').animate({'width':'1000px'},1000);
    callback();
})((function(callback2){
    $('#art2').animate({'width':'1000px'},1000);
    callback2();
})(function(){
    $('#art3').animate({'width':'1000px'},1000);
}));

Trzy animacje rozpoczynają się w tym samym czasie

Gdzie jest mój błąd?


czy masz na myśli wywołanie funkcji dokładnie w 3 5 i 8 sekund, czy tylko jedna po drugiej?
Trass Vasston

Myślę , że po prostu nie masz pewności co do wykonywania funkcji synchronicznych i asynchronicznych. Zaktualizowałem moją odpowiedź poniżej. Mam nadzieję, że to pomoże.
Wayne

Odpowiedzi:


243

W Javascript są funkcje synchroniczne i asynchroniczne .

Funkcje synchroniczne

Większość funkcji w Javascript jest synchroniczna. Gdybyś miał wywołać kilka funkcji synchronicznych z rzędu

doSomething();
doSomethingElse();
doSomethingUsefulThisTime();

wykonają w kolejności. doSomethingElsenie rozpocznie się, dopóki się doSomethingnie zakończy. doSomethingUsefulThisTimez kolei nie rozpocznie się, dopóki się doSomethingElsenie zakończy.

Funkcje asynchroniczne

Jednak funkcje asynchroniczne nie będą na siebie czekać. Spójrzmy na ten sam przykład kodu, który mieliśmy powyżej, tym razem zakładając, że funkcje są asynchroniczne

doSomething();
doSomethingElse();
doSomethingUsefulThisTime();

Funkcje zostaną zainicjowane po kolei, ale wszystkie będą wykonywane mniej więcej w tym samym czasie. Nie możesz konsekwentnie przewidzieć, który z nich zakończy się pierwszy: ten, który zajmie najkrótszy czas, zakończy się jako pierwszy.

Ale czasami chcesz, aby funkcje, które są asynchroniczne, były wykonywane po kolei, a czasami chcesz, aby funkcje, które są synchroniczne, były wykonywane asynchronicznie. Na szczęście jest to możliwe odpowiednio w przypadku wywołań zwrotnych i limitów czasu.

Callback

Załóżmy, że mamy trzy funkcje asynchroniczne, które chcemy wykonać w kolejności some_3secs_function, some_5secs_functioni some_8secs_function.

Ponieważ funkcje mogą być przekazywane jako argumenty w JavaScript, możesz przekazać funkcję jako wywołanie zwrotne do wykonania po jej zakończeniu.

Jeśli stworzymy takie funkcje

function some_3secs_function(value, callback){
  //do stuff
  callback();
}

wtedy możesz zadzwonić po kolei, na przykład:

some_3secs_function(some_value, function() {
  some_5secs_function(other_value, function() {
    some_8secs_function(third_value, function() {
      //All three functions have completed, in order.
    });
  });
});

Limity czasu

W JavaScript możesz nakazać funkcji, aby wykonywała się po określonym czasie (w milisekundach). W efekcie może to spowodować, że funkcje synchroniczne będą działać asynchronicznie.

Jeśli mamy trzy funkcje synchroniczne, możemy je wykonywać asynchronicznie za pomocą setTimeoutfunkcji.

setTimeout(doSomething, 10);
setTimeout(doSomethingElse, 10);
setTimeout(doSomethingUsefulThisTime, 10);

Jest to jednak trochę brzydkie i narusza zasadę DRY [wikipedia] . Moglibyśmy trochę to uporządkować, tworząc funkcję, która akceptuje tablicę funkcji i limit czasu.

function executeAsynchronously(functions, timeout) {
  for(var i = 0; i < functions.length; i++) {
    setTimeout(functions[i], timeout);
  }
}

Można to nazwać tak:

executeAsynchronously(
    [doSomething, doSomethingElse, doSomethingUsefulThisTime], 10);

Podsumowując, jeśli masz funkcje asynchroniczne, które chcesz wykonywać synchronicznie, użyj wywołań zwrotnych, a jeśli masz funkcje synchroniczne, które chcesz wykonywać asynchronicznie, użyj limitów czasu.


7
Nie spowoduje to opóźnienia funkcji o 3,5 i 8 sekund, jak sugeruje przykład, będą one działać jedna po drugiej.
Trass Vasston,

1
@Peter - Czekaj, więc jestem zdezorientowany. Jeśli są to proste wywołania synchroniczne, których wykonanie zajmuje kilka sekund, to po co nam to wszystko?
Wayne

9
@Peter - +1 za najpiękniejszą, zawiłą metodę, jaką kiedykolwiek widziałem, do wywoływania kolejno trzech funkcji synchronicznych.
Wayne

4
Dziękujemy za fachowe wyjaśnienie różnicy między funkcjami async i sync js. To wiele wyjaśnia.
jnelson

2
NIE jest to poprawne z następujących powodów: (1) wszystkie 3 limity czasu znikną po 10 sekundach, więc wszystkie 3 linie zostaną wyzwolone w tym samym czasie. (2) ta metoda wymaga wcześniejszego poznania czasu trwania i „zaplanowania” funkcji w przyszłości, zamiast czekania na rozwiązanie wcześniejszych funkcji asynchronicznych w łańcuchu, które są wyzwalaczem. --- zamiast tego chcesz użyć jednej z poniższych odpowiedzi, używając wywołań zwrotnych, obietnic lub biblioteki asynchronicznej.
zeroasterisk

37

Ta odpowiedź wykorzystuje standardową promisesfunkcję JavaScript ECMAScript 6. Jeśli Twoja platforma docelowa nie obsługuje promises, wypełnij ją PromiseJs .

Spójrz na moją odpowiedź tutaj Poczekaj, aż funkcja z animacjami zostanie zakończona, aż do uruchomienia innej funkcji, jeśli chcesz użyć jQueryanimacji.

Oto, jak wyglądałby Twój kod z ES6 Promisesi jQuery animations.

Promise.resolve($('#art1').animate({ 'width': '1000px' }, 1000).promise()).then(function(){
    return Promise.resolve($('#art2').animate({ 'width': '1000px' }, 1000).promise());
}).then(function(){
    return Promise.resolve($('#art3').animate({ 'width': '1000px' }, 1000).promise());
});

Można również zawijać zwykłe metody Promises.

new Promise(function(fulfill, reject){
    //do something for 5 seconds
    fulfill(result);
}).then(function(result){
    return new Promise(function(fulfill, reject){
        //do something for 5 seconds
        fulfill(result);
    });
}).then(function(result){
    return new Promise(function(fulfill, reject){
        //do something for 8 seconds
        fulfill(result);
    });
}).then(function(result){
    //do something with the result
});

thenMetoda jest wykonywana, gdy tylko Promiseskończył. Zwykle wartość zwracana functionprzekazywana do thenjest przekazywana do następnej jako wynik.

Ale jeśli Promisezostanie zwrócone a, następna thenfunkcja czeka do Promisezakończenia wykonywania i odbiera wyniki (wartość, do której jest przekazywana fulfill).


Wiem, że jest to przydatne, ale podany kod był trudny do zrozumienia bez szukania rzeczywistego przykładu, aby nadać mu jakiś kontekst. Znalazłem ten film na YouTube: youtube.com/watch?v=y5mltEaQxa0 - i napisałem źródło z filmu tutaj drive.google.com/file/d/1NrsAYs1oaxXw0kv9hz7a6LjtOEb6x7z-/ ... Istnieje kilka innych niuansów, takich jak brakujący haczyk w ten przykład, który jest tutaj rozwinięty. (użyj innego identyfikatora w linii getPostById () lub spróbuj zmienić nazwisko autora, aby nie pasowało do postu itp.)
JGFMK,

20

Wygląda na to, że nie w pełni doceniasz różnicę między wykonywaniem funkcji synchronicznych i asynchronicznych .

Kod podany w aktualizacji natychmiast wykonuje każdą z funkcji zwrotnych, co z kolei natychmiast uruchamia animację. Animacje są jednak wykonywane asynchronicznie . Działa to tak:

  1. Wykonaj krok w animacji
  2. Wywołaj setTimeoutfunkcję zawierającą następny krok animacji i opóźnienie
  3. Mija trochę czasu
  4. Wywołanie zwrotne podane do setTimeoutexecute
  5. Wróć do kroku 1

Trwa to do zakończenia ostatniego kroku animacji. W międzyczasie funkcje synchroniczne już dawno zostały zakończone. Innymi słowy, wywołanie animatefunkcji nie zajmuje tak naprawdę 3 sekund. Efekt jest symulowany przez opóźnienia i wywołania zwrotne.

Potrzebujesz kolejki . Wewnętrznie, jQuery kolejki animacji, tylko wykonując swoją oddzwanianie, gdy jej odpowiadających ukończy animacji. Jeśli wywołanie zwrotne rozpocznie kolejną animację, efekt jest taki, że są one wykonywane po kolei.

W najprostszym przypadku jest to równoważne z następującym:

window.setTimeout(function() {
    alert("!");
    // set another timeout once the first completes
    window.setTimeout(function() {
        alert("!!");
    }, 1000);
}, 3000); // longer, but first

Oto ogólna asynchroniczna funkcja pętli. Wywoła podane funkcje po kolei, czekając między nimi określoną liczbę sekund.

function loop() {
    var args = arguments;
    if (args.length <= 0)
        return;
    (function chain(i) {
        if (i >= args.length || typeof args[i] !== 'function')
            return;
        window.setTimeout(function() {
            args[i]();
            chain(i + 1);
        }, 2000);
    })(0);
}    

Stosowanie:

loop(
  function() { alert("sam"); }, 
  function() { alert("sue"); });

Możesz oczywiście zmodyfikować to, aby wykorzystać konfigurowalne czasy oczekiwania lub natychmiast wykonać pierwszą funkcję lub zatrzymać wykonywanie, gdy funkcja w łańcuchu powróci falselub do applyfunkcji w określonym kontekście lub cokolwiek innego, czego możesz potrzebować.


14

Wierzę, że biblioteka asynchroniczna zapewni ci bardzo elegancki sposób na zrobienie tego. Chociaż obietnice i wywołania zwrotne mogą być nieco trudne do pogodzenia, asynchronizacja może zapewnić zgrabne wzorce usprawniające proces myślowy. Aby uruchamiać funkcje szeregowo, należałoby umieścić je w wodospadzie asynchronicznym . W żargonie asynchronicznym każda funkcja nazywa się a, taskktóra pobiera argumenty i callback; która jest następną funkcją w sekwencji. Podstawowa struktura wyglądałaby mniej więcej tak:

async.waterfall([
  // A list of functions
  function(callback){
      // Function no. 1 in sequence
      callback(null, arg);
  },
  function(arg, callback){
      // Function no. 2 in sequence
      callback(null);
  }
],    
function(err, results){
   // Optional final callback will get results for all prior functions
});

Właśnie próbowałem pokrótce wyjaśnić tutaj strukturę. Przeczytaj przewodnik po wodospadzie, aby uzyskać więcej informacji, jest całkiem dobrze napisany.


1
To naprawdę sprawia, że ​​JS jest trochę bardziej znośny.
mike

9

Twoje funkcje powinny przyjmować funkcję zwrotną, która jest wywoływana po zakończeniu.

function fone(callback){
...do something...
callback.apply(this,[]);

}

function ftwo(callback){
...do something...
callback.apply(this,[]);
}

wówczas użycie byłoby takie:

fone(function(){
  ftwo(function(){
   ..ftwo done...
  })
});

4
asec=1000; 

setTimeout('some_3secs_function("somevalue")',asec*3);
setTimeout('some_5secs_function("somevalue")',asec*5);
setTimeout('some_8secs_function("somevalue")',asec*8);

Nie będę tutaj wdawać się w szczegółową dyskusję na temat setTimeout, ale:

  • w tym przypadku dodałem kod do wykonania jako ciąg. jest to najprostszy sposób na przekazanie var do funkcji setTimeout-ed, ale puryści będą narzekać.
  • można również przekazać nazwę funkcji bez cudzysłowów, ale nie można przekazać żadnej zmiennej.
  • twój kod nie czeka na wyzwolenie setTimeout.
  • Ten może być początkowo trudny do zrozumienia: z powodu poprzedniego punktu, jeśli przekażesz zmienną z funkcji wywołującej, ta zmienna nie będzie już istnieć do czasu wyzwolenia limitu czasu - funkcja wywołująca zostanie wykonana i vars zniknął.
  • Znam się na tym, że korzystam z anonimowych funkcji, aby obejść ten problem, ale może istnieć lepszy sposób,

3

Ponieważ oznaczyłeś go za pomocą javascript, użyłbym kontrolki timera, ponieważ nazwy twoich funkcji to 3, 5 i 8 sekund. Więc uruchom stoper, 3 sekundy w, zadzwoń do pierwszego, 5 sekund do drugiego, 8 sekund do trzeciego, a kiedy skończy, zatrzymaj stoper.

Zwykle w JavaScript to, co masz, jest poprawne, ponieważ funkcje działają jedna po drugiej, ale ponieważ wygląda na to, że próbujesz wykonać animację czasową, najlepszym rozwiązaniem będzie zegar.


2

//sample01
(function(_){_[0]()})([
	function(){$('#art1').animate({'width':'10px'},100,this[1].bind(this))},
	function(){$('#art2').animate({'width':'10px'},100,this[2].bind(this))},
	function(){$('#art3').animate({'width':'10px'},100)},
])

//sample02
(function(_){_.next=function(){_[++_.i].apply(_,arguments)},_[_.i=0]()})([
	function(){$('#art1').animate({'width':'10px'},100,this.next)},
	function(){$('#art2').animate({'width':'10px'},100,this.next)},
	function(){$('#art3').animate({'width':'10px'},100)},
]);

//sample03
(function(_){_.next=function(){return _[++_.i].bind(_)},_[_.i=0]()})([
	function(){$('#art1').animate({'width':'10px'},100,this.next())},
	function(){$('#art2').animate({'width':'10px'},100,this.next())},
	function(){$('#art3').animate({'width':'10px'},100)},
]);


Czy możesz wyjaśnić, co to jest? Co jest związane z podkreśleniem? Do czego służy przypisana funkcja next?
mtso

1
Przykład 2 wyjaśniam za pomocą jsfiddle. jsfiddle.net/mzsteyuy/3 Jeśli pozwolisz mi z grubsza wyjaśnić, przykład 2 jest krótką wersją kodu w jsfiddle. podkreśleniem jest Array, które elementy są waloryzowane (i), a funkcja next i function [0] ~ [2].
yuuya,

1

Możesz również wykorzystać obietnice w ten sposób:

    some_3secs_function(this.some_value).then(function(){
       some_5secs_function(this.some_other_value).then(function(){
          some_8secs_function(this.some_other_other_value);
       });
    });

Trzeba by zrobić some_valueglobalne, aby uzyskać do niego dostęp od wewnątrz .Następnie

Alternatywnie, z funkcji zewnętrznej możesz zwrócić wartość, której użyłaby funkcja wewnętrzna, na przykład:

    one(some_value).then(function(return_of_one){
       two(return_of_one).then(function(return_of_two){
          three(return_of_two);
       });
    });

0

Używam funkcji „waitUntil” opartej na setTimeout javascript

/*
    funcCond : function to call to check whether a condition is true
    readyAction : function to call when the condition was true
    checkInterval : interval to poll <optional>
    timeout : timeout until the setTimeout should stop polling (not 100% accurate. It was accurate enough for my code, but if you need exact milliseconds, please refrain from using Date <optional>
    timeoutfunc : function to call on timeout <optional>
*/
function waitUntil(funcCond, readyAction, checkInterval, timeout, timeoutfunc) {
    if (checkInterval == null) {
        checkInterval = 100; // checkinterval of 100ms by default
    }
    var start = +new Date(); // use the + to convert it to a number immediatly
    if (timeout == null) {
        timeout = Number.POSITIVE_INFINITY; // no timeout by default
    }
    var checkFunc = function() {
        var end = +new Date(); // rough timeout estimations by default

        if (end-start > timeout) {
            if (timeoutfunc){ // if timeout function was defined
                timeoutfunc(); // call timeout function
            }
        } else {
            if(funcCond()) { // if condition was met
                readyAction(); // perform ready action function
            } else {
                setTimeout(checkFunc, checkInterval); // else re-iterate
            }
        }
    };
    checkFunc(); // start check function initially
};

To działałoby idealnie, gdyby funkcje ustawiały pewien warunek na true, który byłby w stanie sondować. Dodatkowo jest wyposażony w limity czasu, które oferują alternatywy na wypadek, gdyby funkcja nie wykonała czegoś (nawet w przedziale czasowym. Pomyśl o opiniach użytkowników!)

na przykład

doSomething();
waitUntil(function() { return doSomething_value===1;}, doSomethingElse);
waitUntil(function() { return doSomethingElse_value===1;}, doSomethingUseful);

Uwagi

Data powoduje zgrubne oszacowanie limitu czasu. Aby uzyskać większą precyzję, przełącz się na funkcje takie jak console.time (). Zwróć uwagę, że Date oferuje lepszą obsługę różnych przeglądarek i starszych wersji. Jeśli nie potrzebujesz dokładnych pomiarów w milisekundach; nie przejmuj się, lub, alternatywnie, zawiń go i zaoferuj console.time (), gdy przeglądarka to obsługuje


0

Jeśli metoda 1 ma zostać wykonana po metodzie 2, 3, 4. Poniższy fragment kodu może być rozwiązaniem tego problemu przy użyciu obiektu Deferred w JavaScript.

function method1(){
  var dfd = new $.Deferred();
     setTimeout(function(){
     console.log("Inside Method - 1"); 
     method2(dfd);	 
    }, 5000);
  return dfd.promise();
}

function method2(dfd){
  setTimeout(function(){
   console.log("Inside Method - 2"); 
   method3(dfd);	
  }, 3000);
}

function method3(dfd){
  setTimeout(function(){
   console.log("Inside Method - 3"); 	
   dfd.resolve();
  }, 3000);
}

function method4(){   
   console.log("Inside Method - 4"); 	
}

var call = method1();

$.when(call).then(function(cb){
  method4();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.