Executando verificação de segurança...
2

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 o console.log('f1'); e em seguida executa callback1
  • mas callback1 chama f2(callback2)
  • dentro de f2, chama o console.log('f2'); e em seguida executa callback2
  • mas callback2 chama f3(callback3)
  • dentro de f3, chama o console.log('f3'); e em seguida executa callback3
  • callback3 chama console.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 recebe f3 como argumento (ou seja, f3 é o callback de f2, já f3 não tem parênteses - não está sendo chamada, e sim passada como argumento para f2)
  • só que f2 está sendo chamada, então primeiro ela precisa terminar sua execução, para que o resultado seja passado para f1
  • por isso f2 é executada primeiro. E como seu callback é f3, esta é executada em seguida. Mas como f3 é chamada sem argumentos, entra no else
  • depois que f2 terminou, seu retorno é passado para f1. Mas f2 não retorna nada (não tem nenhum return), então nesses casos o JS definiu que o retorno é undefined
  • ou seja, f1 recebe undefined como callback, e como undefined é um valor considerado falso, entra no else

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

Carregando publicação patrocinada...