Entendendo melhor as funções de retorno de chamada em JavaScript

163

Entendo passar uma função para outra como retorno de chamada e executá-la, mas não estou entendendo a melhor implementação para fazer isso. Estou procurando um exemplo muito básico, como este:

var myCallBackExample = {
    myFirstFunction : function( param1, param2, callback ) {
        // Do something with param1 and param2.
        if ( arguments.length == 3 ) {
            // Execute callback function.
            // What is the "best" way to do this?
        }
    },
    mySecondFunction : function() {
        myFirstFunction( false, true, function() {
            // When this anonymous function is called, execute it.
        });
    }
};

No myFirstFunction, se eu retornar um novo retorno de chamada (), ele funcionará e executará a função anônima, mas essa não parece a abordagem correta para mim.


fonte
Correto em que sentido? Normalmente, os retornos de chamada são usados ​​para manipuladores de eventos - principalmente as chamadas Ajax, que são assíncronas - basicamente coisas nas quais você não sabe quando (ou se) um resposne virá.
Cletus
2
pela forma como os argumentos são matriz como, mas não array, para que você não pode fazer argument.length mas você pode convertê-lo em uma matriz usando o método slice ...
paul
1
@ paul, embora você esteja certo de que argumentsnão é um array, você ainda pode fazer referência ao seu comprimento como arguments.length- experimente. Esta propriedade refere-se ao número de argumentos realmente transmitidos e não necessariamente ao número de parâmetros na assinatura da função.
hotshot309

Respostas:

132

Você pode apenas dizer

callback();

Como alternativa, você pode usar o callmétodo se desejar ajustar o valor thisdentro do retorno de chamada.

callback.call( newValueForThis);

Dentro da função thisseria o que newValueForThisfor.

krosenvold
fonte
91

Você deve verificar se o retorno de chamada existe e é uma função executável:

if (callback && typeof(callback) === "function") {
    // execute the callback, passing parameters as necessary
    callback();
}

Muitas bibliotecas (jQuery, dojo etc.) usam um padrão semelhante para suas funções assíncronas, assim como o node.js para todas as funções assíncronas (o nodejs geralmente passa errore datapara o retorno de chamada). Olhar para o código fonte ajudaria!

arunjitsingh
fonte
Por que você transmite callbackpara string e depois verifica seu tipo? Isso melhorará o desempenho? É como verificar o tipo, verificar se o booleano convertido retorna true e, em seguida, verificar seu tipo novamente e testá-lo com a string ... Você poderia explicar o porquê?
headacheCoder
Estou curioso para saber por que você precisa da primeira asserção para retorno de chamada ... é para verificar nulo ou indefinido? Não typeof(callback)conseguiria isso para você? typeof(null) === "Object",typeof("undefined") === "undefined"
PJH
1
Curto-circuito E. Se o retorno de chamada não existir, não se preocupe em calcular seu tipo. Mas você está certo. Não é necessário com o typeof (), mas vou fazer um jsperf e ver se o curto-circuito vale a pena.
arunjitsingh
@headacheCoder - callbacknão está sendo convertido para uma string, seu tipo está sendo verificado para verificar se é uma função antes de ser chamada. O código presumivelmente aceita callbackcomo argumento e é incerto que o argumento seja de um tipo que pode ser chamado - ou talvez os argumentos sejam de vários tipos na tentativa de fornecer uma forma de polimorfismo em que o código possa reagir de maneira diferente a typeofargumentos diferentes .
LeeGee
34

Existem 3 possibilidades principais para executar uma função:

var callback = function(x, y) {
    // "this" may be different depending how you call the function
    alert(this);
};
  1. retorno de chamada (argumento_1, argumento_2);
  2. callback.call (algum_objeto, argumento_1, argumento_2);
  3. callback.apply (algum_objeto, [argumento_1, argumento_2]);

O método escolhido depende se:

  1. Você tem os argumentos armazenados em uma matriz ou como variáveis ​​distintas.
  2. Você deseja chamar essa função no contexto de algum objeto. Nesse caso, o uso da palavra-chave "this" nesse retorno de chamada faria referência ao objeto passado como argumento em call () ou apply (). Se você não deseja passar o contexto do objeto, use nulo ou indefinido. No último caso, o objeto global seria usado para "isso".

Documentos para Function.call , Function.apply

Ionuț G. Stan
fonte
6

Os retornos de chamada são sobre sinais e "novo" é sobre a criação de instâncias de objetos.

Nesse caso, seria ainda mais apropriado executar apenas "callback ();" do que "retornar novo retorno de chamada ()" porque você não está fazendo nada com um valor de retorno de qualquer maneira.

(E o teste argument.length == 3 é realmente desajeitado, fwiw, é melhor verificar se o parâmetro de retorno de chamada existe e é uma função.)

annakata
fonte
6

a implementação adequada seria:

if( callback ) callback();

isso torna o parâmetro de retorno de chamada opcional.

faeb187
fonte
E se o argumento de retorno de chamada não for uma função?
Yaki Klein
2

Você pode usar:

if (callback && typeof(callback) === "function") {
    callback();
}

O exemplo abaixo é um pouco mais abrangente:

function mySandwich(param1, param2, callback) {
  alert('Started eating my sandwich.\n\nIt has: ' + param1 + ', ' + param2);
  var sandwich = {
      toppings: [param1, param2]
    },
    madeCorrectly = (typeof(param1) === "string" && typeof(param2) === "string") ? true : false;
  if (callback && typeof(callback) === "function") {
    callback.apply(sandwich, [madeCorrectly]);
  }
}

mySandwich('ham', 'cheese', function(correct) {
  if (correct) {
    alert("Finished eating my " + this.toppings[0] + " and " + this.toppings[1] + " sandwich.");
  } else {
    alert("Gross!  Why would I eat a " + this.toppings[0] + " and " + this.toppings[1] + " sandwich?");
  }
});

Hasan A Yousef
fonte
1

Aqui está um exemplo básico que explica a callback()função em JavaScript:

var x = 0;

function testCallBack(param1, param2, callback) {
  alert('param1= ' + param1 + ', param2= ' + param2 + ' X=' + x);
  if (callback && typeof(callback) === "function") {
    x += 1;
    alert("Calla Back x= " + x);
    x += 1;
    callback();
  }
}

testCallBack('ham', 'cheese', function() {
  alert("Function X= " + x);
});

JSFiddle

BERGUIGA Mohamed Amine
fonte
1

function checkCallback(cb) {
  if (cb || cb != '') {
    if (typeof window[cb] === 'undefined') alert('Callback function not found.');
    else window[cb].call(this, Arg1, Arg2);
  }
}

Aamir Afridi
fonte