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
callback1chamaf2(callback2) - dentro de
f2, chama oconsole.log('f2');e em seguida executacallback2 - mas
callback2chamaf3(callback3) - dentro de
f3, chama oconsole.log('f3');e em seguida executacallback3 callback3chamaconsole.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:f2está sendo executada, e recebef3como argumento (ou seja,f3é o callback def2, jáf3não tem parênteses - não está sendo chamada, e sim passada como argumento paraf2) - só que
f2está 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
f2terminou, seu retorno é passado paraf1. Masf2não retorna nada (não tem nenhumreturn), então nesses casos o JS definiu que o retorno éundefined - ou seja,
f1recebeundefinedcomo 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).