Só pra complementar o que já foi dito, o setTimeout
só está servindo para confundir. Isso porque cada callback vai executar depois de determinado tempo, e como os tempos são diferentes, isso acaba confundindo, pois você não sabe se algo executou depois porque realmente foi esta a ordem, ou é só porque o tempo era maior.
Então para simplificar, e deixar mais claro o que está acontecendo, mudei as suas funções, retirando o setTimeout
e deixando-as assim:
function f1(callback) {
console.log('f1');
if (callback)
callback();
else
console.log('f1 sem callback');
}
function f2(callback) {
console.log('f2');
if (callback)
callback();
else
console.log('f2 sem callback');
}
function f3(callback) {
console.log('f3');
if (callback)
callback();
else
console.log('f3 sem callback');
}
Agora, ao rodar o primeiro exemplo:
f1(function() {
f2(function() {
f3(function() {
console.log('Terminei');
})
})
});
A saída é:
f1
f2
f3
Terminei
Pois primeiro ele chama f1
, passando uma função anônima (function () { etc
). Esta função anônima é o callback passado para f1
. E como f1
executa o callback, então ela será executada. E dentro dela é chamada f2
, que por sua vez recebe outro callback, que por fim chama f3
.
No fundo, este código é equivalente a:
function callback1() {
f2(callback2)
}
function callback2() {
f3(callback3);
}
function callback3() {
console.log('Terminei');
}
f1(callback1);
A diferença é que o primeiro código, em vez de ter as funções callbackX
, usou funções anônimas, e foi tudo feito de uma vez.
Olhando o código acima, agora dá pra entender porque executa na ordem f1
, f2
, f3
:
f1(callback1)
é executada- dentro de
f1
, chama oconsole.log('f1');
e em seguida executacallback1
- mas
callback1
chamaf2(callback2)
- dentro de
f2
, chama oconsole.log('f2');
e em seguida executacallback2
- mas
callback2
chamaf3(callback3)
- dentro de
f3
, chama oconsole.log('f3');
e em seguida executacallback3
callback3
chamaconsole.log('Terminei');
Prosseguindo com seus exemplos, se executarmos f1()
, a saída é:
f1
f1 sem callback
Repare que agora chamamos f1()
sem passar nenhum argumento, portanto o parâmetro callback
não será setado e entrará no else
.
E se chamarmos f1(f2)
, a saída é:
f1
f2
f2 sem callback
Pois agora estamos chamando f1
e passando f2
como argumento. Repare que f2
, sem os parênteses, indica que estou passando a função como argumento (em vez de executá-la). Neste caso, f2
é o callback
. Então dentro de f1
ele verifica se o callback está setado, e como está, então executa. Por isso f2
é executada, mas como ela é chamada sem argumentos, então f2
não recebe um callback acaba entrando no else
.
E se fizermos f1(f2(f3))
, a saída será:
f2
f3
f3 sem callback
f1
f1 sem callback
Agora é o seguinte:
f1
é chamada com um argumento, que éf2(f3)
- mas
f2(f3)
é outra chamada:f2
está sendo executada, e recebef3
como argumento (ou seja,f3
é o callback def2
, jáf3
não tem parênteses - não está sendo chamada, e sim passada como argumento paraf2
) - só que
f2
está sendo chamada, então primeiro ela precisa terminar sua execução, para que o resultado seja passado paraf1
- por isso
f2
é executada primeiro. E como seu callback éf3
, esta é executada em seguida. Mas comof3
é chamada sem argumentos, entra noelse
- depois que
f2
terminou, seu retorno é passado paraf1
. Masf2
não retorna nada (não tem nenhumreturn
), então nesses casos o JS definiu que o retorno éundefined
- ou seja,
f1
recebeundefined
como callback, e comoundefined
é um valor considerado falso, entra noelse
Por isso que f1
acaba sendo executada por último. Porque a chamada f2(f3)
precisa terminar, para que seu retorno seja passado para f1
.
Agora sobre f1(f2(f3()))
. O resultado é:
f3
f3 sem callback
f2
f2 sem callback
f1
f1 sem callback
Repare que f3()
(com parênteses) está chamando f3
, ou seja f2(f3())
está dizendo que f2
recebe como argumento o resultado da execução de f3
. Por isso f3
tem que executar primeiro, pois só depois que ela terminar que o valor retornado pode ser passado para f2
. E da mesma forma, o resultado de f2
é passado para f1
, por isso que f2
tem que executar antes de f1
.
Se quer respeitar a ordem, depende muito do que precisa. Uma função precisa do resultado da outra? Elas podem ser chamadas de maneira independente? Etc.
No seu exemplo não fica claro qual deve ser a ordem correta, até porque as funções não fazem nada de especial.
De qualquer forma, a ideia de receber um callback é: "me passe a função que será executada depois". O callback é isso, vc indica o que vai ser executado, mas o quando é decidido pela função que o recebeu (algumas podem ser imediatamente, outras só quando determinada condição ocorrer - por exemplo, no setTimeout
, só executa depois que o tempo indicado se passou).