Criar um retorno de chamada personalizado em JavaScript

322

Tudo o que preciso fazer é executar uma função de retorno de chamada quando minha execução atual da função terminar.

function LoadData() 
{
    alert('The data has been loaded');
    //Call my callback with parameters. For example,
    //callback(loadedData , currentObject);
}

Um consumidor para esta função deve ser assim:

object.LoadData(success);

function success(loadedData , currentObject) 
{
  //Todo: some action here 
}

Como faço para implementar isso?

Amgad Fahmi
fonte
3
object.LoadData(success)A chamada deve ser posterior à function success definição. Caso contrário, você receberá um erro informando que a função não está definida.
31311 J. Bruni

Respostas:

574

Na verdade, seu código funcionará como está, apenas declare seu retorno de chamada como argumento e você poderá chamá-lo diretamente usando o nome do argumento.

O básico

function doSomething(callback) {
    // ...

    // Call the callback
    callback('stuff', 'goes', 'here');
}

function foo(a, b, c) {
    // I'm the callback
    alert(a + " " + b + " " + c);
}

doSomething(foo);

Isso chamará doSomething, o que chamará foo, o que alertará "as coisas estão aqui".

Observe que é muito importante passar a referência de função ( foo), em vez de chamar a função e passar seu resultado ( foo()). Na sua pergunta, você faz isso corretamente, mas vale a pena destacar porque é um erro comum.

Coisas mais avançadas

Às vezes, você deseja chamar o retorno de chamada para que ele veja um valor específico para this. Você pode fazer isso facilmente com a callfunção JavaScript :

function Thing(name) {
    this.name = name;
}
Thing.prototype.doSomething = function(callback) {
    // Call our callback, but using our own instance as the context
    callback.call(this);
}

function foo() {
    alert(this.name);
}

var t = new Thing('Joe');
t.doSomething(foo);  // Alerts "Joe" via `foo`

Você também pode passar argumentos:

function Thing(name) {
    this.name = name;
}
Thing.prototype.doSomething = function(callback, salutation) {
    // Call our callback, but using our own instance as the context
    callback.call(this, salutation);
}

function foo(salutation) {
    alert(salutation + " " + this.name);
}

var t = new Thing('Joe');
t.doSomething(foo, 'Hi');  // Alerts "Hi Joe" via `foo`

Às vezes, é útil passar os argumentos que você deseja fornecer ao retorno de chamada como uma matriz, em vez de individualmente. Você pode usar applypara fazer isso:

function Thing(name) {
    this.name = name;
}
Thing.prototype.doSomething = function(callback) {
    // Call our callback, but using our own instance as the context
    callback.apply(this, ['Hi', 3, 2, 1]);
}

function foo(salutation, three, two, one) {
    alert(salutation + " " + this.name + " - " + three + " " + two + " " + one);
}

var t = new Thing('Joe');
t.doSomething(foo);  // Alerts "Hi Joe - 3 2 1" via `foo`
TJ Crowder
fonte
Eu sei que isso vai funcionar se eu não tenho quaisquer parâmetros, como o exemplo que você escreveu, mas quando eu tento passar uma função com parâmetros que ele está jogando uma exceção e me dizendo que a função não está definida
Amgad Fahmi
@TiTaN: Isso é estranho, não há nada de especial em passar parâmetros para o retorno de chamada. A referência de retorno de chamada que você passa para sua função é uma referência de função como qualquer outra, você pode fazer todas as coisas normais com ela.
TJ Crowder
4
@ quem respondeu: Acho que o problema do TiTaN é que ele não sabe como passar uma função que requer argumentos para um retorno de chamada que não passa nenhum argumento. Pense setTimeout(). A resposta é encerrar o retorno de chamada em um fechamento:doSomething(function(){foo('this','should','work')})
slebetman
Alguém aponta o TiTaN para um tópico (de preferência no SO) que discute a questão acima, minha busca-fu está fraca hoje.
slebetman
1
@Webwoman - Depende do seu caso de uso. Você pode passá-lo como argumento ou incluí-lo em algum tipo de objeto de configurações / opções ou em qualquer outra opção.
TJ Crowder
77

É uma boa prática garantir que o retorno de chamada seja uma função real antes de tentar executá-lo:

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

  callback();
}
Donald A Nummer Jr
fonte
21
if(typeof callback == "function")terá o mesmo resultado.
Reactgular
22
Sim, mas se não houver retorno de chamada, por que se incomodar em digitar? Esse é o ponto de callback && ...
theonlygusti
61

Meus 2 centavos. O mesmo, mas diferente ...

<script>
    dosomething("blaha", function(){
        alert("Yay just like jQuery callbacks!");
    });


    function dosomething(damsg, callback){
        alert(damsg);
        if(typeof callback == "function") 
        callback();
    }
</script>
K. Kilian Lindberg
fonte
7
Eu amo esse trecho, eu estava procurando por isso
vimal1083
10
function loadData(callback) {

    //execute other requirement

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

loadData(function(){

   //execute callback

});
Arun Bahal
fonte
6
Considere editar sua postagem para adicionar mais explicações sobre o que seu código faz e por que ele resolverá o problema. Uma resposta que geralmente contém apenas código (mesmo que esteja funcionando) geralmente não ajuda o OP a entender seu problema. No entanto, nesse caso, essa é uma pergunta muito antiga, com respostas altamente conceituadas já postadas. Pode não valer a pena responder isso quando houver perguntas mais recentes que possam ser feitas com mais atenção.
SuperBiasedMan
1
Eu gosto desta resposta, sua demonstração estr8 do que as pessoas querem ver.
Aft3rL1f3 23/03
5
   function callback(e){
      return e;
   }
    var MyClass = {
       method: function(args, callback){
          console.log(args);
          if(typeof callback == "function")
          callback();
       }    
    }

==============================================

MyClass.method("hello",function(){
    console.log("world !");
});

==============================================

O resultado é:

hello world !
Eyad Farra
fonte
4

Se você deseja executar uma função quando algo é feito. Uma de uma boa solução é ouvir eventos. Por exemplo, implementarei Dispatcheruma DispatcherEventclasse com ES6 e, em seguida:

let Notification = new Dispatcher()
Notification.on('Load data success', loadSuccessCallback)

const loadSuccessCallback = (data) =>{
   ...
}
//trigger a event whenever you got data by
Notification.dispatch('Load data success')

Expedidor:

class Dispatcher{
  constructor(){
    this.events = {}
  }

  dispatch(eventName, data){
    const event = this.events[eventName]
    if(event){
      event.fire(data)
    }
  }

  //start listen event
  on(eventName, callback){
    let event = this.events[eventName]
    if(!event){
      event = new DispatcherEvent(eventName)
      this.events[eventName] = event
    }
    event.registerCallback(callback)
  }

  //stop listen event
  off(eventName, callback){
    const event = this.events[eventName]
    if(event){
      delete this.events[eventName]
    }
  }
}

DispatcherEvent:

class DispatcherEvent{
  constructor(eventName){
    this.eventName = eventName
    this.callbacks = []
  }

  registerCallback(callback){
    this.callbacks.push(callback)
  }

  fire(data){
    this.callbacks.forEach((callback=>{
      callback(data)
    }))
  }
}

Feliz codificação!

p / s: Meu código está ausente, lida com algumas exceções de erro

hien
fonte
1
function LoadData(callback) 
{
    alert('the data have been loaded');
    callback(loadedData, currentObject);
}
Thomas Bonini
fonte
1

Ao chamar a função de retorno de chamada, poderíamos usá-la como abaixo:

consumingFunction(callbackFunctionName)

Exemplo:

// Callback function only know the action,
// but don't know what's the data.
function callbackFunction(unknown) {
  console.log(unknown);
}

// This is a consuming function.
function getInfo(thenCallback) {
  // When we define the function we only know the data but not
  // the action. The action will be deferred until excecuting.
  var info = 'I know now';
  if (typeof thenCallback === 'function') {
    thenCallback(info);    
  }
}

// Start.
getInfo(callbackFunction); // I know now

Este é o Codepend com exemplo completo.

Eric Tan
fonte
1

Algumas das respostas, embora corretas, podem ser um pouco difíceis de entender. Aqui está um exemplo em termos leigos:

var users = ["Sam", "Ellie", "Bernie"];

function addUser(username, callback)
{
    setTimeout(function()
    {
        users.push(username);
        callback();
    }, 200);
}

function getUsers()
{
    setTimeout(function()
    {
        console.log(users);
    }, 100);
}

addUser("Jake", getUsers);

O retorno de chamada significa que "Jake" é sempre adicionado aos usuários antes de exibir a lista de usuários console.log.

Fonte (YouTube)

Dan Bray
fonte
0

Experimentar:

function LoadData (callback)
{
    // ... Process whatever data
    callback (loadedData, currentObject);
}

Funções são de primeira classe em JavaScript ; você pode simplesmente passar por eles.

K Prime
fonte