Como devo chamar 3 funções para executá-las uma após a outra?


151

Se eu precisar chamar essas funções uma após a outra,

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

Eu sei no jQuery que eu poderia fazer algo como:

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

Mas, vamos supor que eu não estou usando jQuery e desejo chamar:

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

Como devo chamar essas funções para executar some_3secs_functione APÓS a chamada terminar, depois executar some_5secs_functione APÓS a chamada terminar, e depois ligar some_8secs_function?

ATUALIZAR:

Isso ainda não está funcionando:

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

Três animações começam ao mesmo tempo

Onde está meu erro?


você quer que as funções sejam chamadas exatamente em 3 5 e 8 segundos ou apenas uma após a outra?
você precisa saber é o seguinte

Eu acho que você simplesmente não tem certeza sobre a execução da função síncrona vs. assíncrona. Atualizei minha resposta abaixo. Espero que ajude.
Wayne

Respostas:


243

Em Javascript, existem funções síncronas e assíncronas .

Funções síncronas

A maioria das funções em Javascript é síncrona. Se você chamar várias funções síncronas seguidas

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

eles serão executados em ordem. doSomethingElsenão será iniciado até que doSomethingseja concluído. doSomethingUsefulThisTime, por sua vez, não será iniciado até que doSomethingElseseja concluído.

Funções assíncronas

A função assíncrona, no entanto, não espera um pelo outro. Vejamos o mesmo exemplo de código que tivemos acima, desta vez assumindo que as funções são assíncronas

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

As funções serão inicializadas em ordem, mas todas serão executadas aproximadamente ao mesmo tempo. Você não pode prever consistentemente qual terminará primeiro: aquele que levar o menor tempo para executar terminará primeiro.

Mas, às vezes, você deseja que funções assíncronas sejam executadas em ordem e, às vezes, que funções síncronas sejam executadas de forma assíncrona. Felizmente, isso é possível com retornos de chamada e tempos limite, respectivamente.

Retornos de chamada

Vamos supor que temos três funções assíncronas que queremos executar em ordem, some_3secs_function, some_5secs_function, e some_8secs_function.

Como as funções podem ser passadas como argumentos em Javascript, você pode passar uma função como retorno de chamada para executar após a conclusão da função.

Se criarmos funções como esta

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

então você pode ligar então em ordem, assim:

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

Timeouts

Em Javascript, você pode dizer a uma função para executar após um certo tempo limite (em milissegundos). Com efeito, isso pode fazer com que as funções síncronas se comportem de forma assíncrona.

Se tivermos três funções síncronas, podemos executá-las de forma assíncrona usando a setTimeoutfunção

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

Isso é, no entanto, um pouco feio e viola o princípio DRY [wikipedia] . Poderíamos limpar isso um pouco, criando uma função que aceite uma matriz de funções e um tempo limite.

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

Isso pode ser chamado assim:

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

Em resumo, se você possui funções assíncronas que deseja executar de forma síncrona, use retornos de chamada e se tiver funções síncronas que deseja executar de forma assíncrona, use tempos limite.


7
Isso não atrasará as funções por 3,5 e 8 segundos, conforme sugerido no exemplo, elas serão executadas uma após a outra.
Trass Vasston

1
@ Peter - Espere, então estou confuso. Se essas chamadas síncronas são simples e demoram alguns segundos para serem concluídas, por que precisamos disso?
Wayne

9
@ Peter - +1 para o método mais bonito e complicado que eu já vi ao chamar três funções síncronas em sequência.
Wayne

4
Obrigado por explicar habilmente a diferença entre as funções assíncrona e sincronizada js. Isso explica muito.
jnelson

2
Isso NÃO está correto pelos seguintes motivos: (1) os 3 tempos limite serão resolvidos após 10 segundos; portanto, todas as 3 linhas serão acionadas ao mesmo tempo. (2) esse método exige que você saiba a duração antecipada e as funções de "agendamento" que acontecerão no futuro, em vez de esperar que as funções assíncronas anteriores da cadeia sejam resolvidas e que esse é o gatilho. --- em vez disso, você deseja usar uma das seguintes respostas usando retornos de chamada, promessas ou a biblioteca assíncrona.
zeroasterisk

37

Esta resposta usa promises, um recurso JavaScript do ECMAScript 6padrão. Se sua plataforma de destino não suportar promises, preencha com o PromiseJs .

Veja minha resposta aqui. Aguarde até que uma Função com animações termine até executar outra Função, se você quiser usar jQueryanimações.

Aqui está a aparência do seu código com ES6 Promisese 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());
});

Métodos normais também podem ser envolvidos 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
});

O thenmétodo é executado assim que Promisefinalizado. Normalmente, o valor de retorno do functionpassado para thené passado para o próximo como resultado.

Mas se a Promisefor retornado, a próxima thenfunção aguardará a Promiseexecução concluída e receberá os resultados (o valor que é passado para fulfill).


Sei que isso é útil, mas achei o código difícil de entender sem procurar um exemplo do mundo real para dar algum contexto. Encontrei este vídeo no YouTube: youtube.com/watch?v=y5mltEaQxa0 - e escrevi a fonte do vídeo aqui drive.google.com/file/d/1NrsAYs1oaxXw0kv9hz7a6LjtOEb6x7z-/… Há mais algumas nuances como a captura ausente este exemplo em que isso é elaborado. (use um ID diferente na linha getPostById () ou tente alterar o nome de um autor para que ele não coincidir com um post etc)
JGFMK

20

Parece que você não está apreciando completamente a diferença entre a execução de funções síncronas e assíncronas .

O código que você forneceu na atualização executa imediatamente cada uma das funções de retorno de chamada, que, por sua vez, iniciam imediatamente uma animação. As animações, no entanto, são executadas assincronamente . Funciona assim:

  1. Execute uma etapa na animação
  2. Ligue setTimeoutcom uma função que contém a próxima etapa da animação e um atraso
  3. Algum tempo passa
  4. O retorno de chamada fornecido para setTimeoutexecuta
  5. Volte ao passo 1

Isso continua até que a última etapa da animação seja concluída. Enquanto isso, suas funções síncronas foram concluídas há muito tempo. Em outras palavras, sua chamada para a animatefunção não leva realmente três segundos. O efeito é simulado com atrasos e retornos de chamada.

O que você precisa é de uma fila . Internamente, o jQuery enfileira as animações, executando apenas o retorno de chamada quando a animação correspondente é concluída. Se o seu retorno de chamada iniciar outra animação, o efeito é que eles serão executados em sequência.

No caso mais simples, isso é equivalente ao seguinte:

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

Aqui está uma função de loop assíncrona geral. Ele chamará as funções fornecidas em ordem, aguardando o número especificado de segundos entre cada uma.

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

Uso:

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

Obviamente, você pode modificar isso para obter tempos de espera configuráveis ​​ou para executar imediatamente a primeira função ou para interromper a execução quando uma função na cadeia retornar falseou para applyas funções em um contexto especificado ou o que mais você precisar.


14

Acredito que a biblioteca assíncrona fornecerá uma maneira muito elegante de fazer isso. Embora promessas e retornos de chamada possam ser um pouco difíceis de conciliar, o assíncrono pode fornecer padrões simples para otimizar seu processo de pensamento. Para executar funções em série, você precisará colocá-las em uma cascata assíncrona . Na linguagem assíncrona, toda função é chamada a taskque recebe alguns argumentos e a callback; qual é a próxima função na sequência. A estrutura básica seria algo como:

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

Eu apenas tentei explicar brevemente a estrutura aqui. Leia o guia em cascata para obter mais informações, é muito bem escrito.


1
Isso realmente torna o JS um pouco mais suportável.
mike

9

suas funções devem ter uma função de retorno de chamada, que é chamada quando termina.

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

}

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

então o uso seria como:

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

Não vou entrar em uma discussão profunda sobre setTimeout aqui, mas:

  • neste caso, adicionei o código para executar como uma string. esta é a maneira mais simples de passar um var para a função setTimeout-ed, mas os puristas se queixam.
  • você também pode passar um nome de função sem aspas, mas nenhuma variável pode ser passada.
  • seu código não espera que o setTimeout seja acionado.
  • Essa pode ser difícil de entender no início: por causa do ponto anterior, se você passar uma variável da sua função de chamada, essa variável não existirá mais no momento em que o tempo limite for acionado - a função de chamada será executada e será executada. Vars se foram.
  • Eu sou conhecido por usar funções anônimas para contornar tudo isso, mas poderia muito bem haver uma maneira melhor,

3

Como você o marcou com javascript, eu usaria um controle de timer, já que seus nomes de função são 3, 5 e 8 segundos. Portanto, inicie o cronômetro, 3 segundos, ligue para o primeiro, 5 segundos para o segundo, 8 segundos para o terceiro e, quando terminar, pare o temporizador.

Normalmente, em Javascript, o que você tem é correto, pois as funções estão sendo executadas uma após a outra, mas, como parece que você está tentando fazer uma animação programada, um cronômetro seria sua melhor aposta.


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)},
]);


Você pode, por favor, explicar o que é isso? O que está vinculado ao sublinhado? O que a função atribuída nextfaz?
Mtso

1
Eu explico a amostra 2 usando jsfiddle. jsfiddle.net/mzsteyuy/3 Se você me permitir explicar mais ou menos, a amostra 2 é uma forma curta de codificar o jsfiddle. o sublinhado é Array, cujos elementos são contra-possíveis (i) e funcionam a seguir e funcionam [0] ~ [2].
yuuya

1

Você também pode usar promessas desta maneira:

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

Você precisaria se tornar some_valueglobal para acessá-lo de dentro do .then

Como alternativa, a partir da função externa, você poderia retornar o valor que a função interna usaria, assim:

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

0

Eu uso uma função 'waitUntil' com base no setTimeout do 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
};

Isso funcionaria perfeitamente se suas funções definissem uma determinada condição como verdadeira, que você seria capaz de pesquisar. Além disso, ele vem com tempos limites, que oferecem alternativas para o caso de sua função falhar em fazer alguma coisa (mesmo dentro do intervalo de tempo. Pense no feedback do usuário!)

por exemplo

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

Notas

A data causa estimativas aproximadas de tempo limite. Para maior precisão, alterne para funções como console.time (). Observe que o Date oferece mais suporte herdado e entre navegadores. Se você não precisar de medições exatas em milissegundos; não se incomode ou, alternativamente, envolva-o e ofereça console.time () quando o navegador suportar


0

Se o método 1 precisar ser executado após o método 2, 3, 4. O seguinte snippet de código pode ser a solução para isso usando o objeto Adiado em 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>

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.