Szukaj…


Proste przykłady użycia wywołania zwrotnego

Oddzwanianie oferuje sposób na rozszerzenie funkcjonalności funkcji (lub metody) bez zmiany jej kodu. Takie podejście jest często stosowane w modułach (bibliotekach / wtyczkach), których kodu nie należy zmieniać.

Załóżmy, że napisaliśmy następującą funkcję, obliczającą sumę podanej tablicy wartości:

function foo(array) {
    var sum = 0;
    for (var i = 0; i < array.length; i++) {
        sum += array[i];
    }
    return sum;
}

Załóżmy teraz, że chcemy coś zrobić z każdą wartością tablicy, np. Wyświetlić ją za pomocą alert() . Możemy wprowadzić odpowiednie zmiany w kodzie foo , takie jak to:

function foo(array) {
    var sum = 0;
    for (var i = 0; i < array.length; i++) {
        alert(array[i]);
        sum += array[i];
    }
    return sum;
}

Ale co, jeśli zdecydujemy się użyć console.log zamiast alert() ? Oczywiście zmiana kodu foo , ilekroć zdecydujemy się zrobić coś z każdą wartością, nie jest dobrym pomysłem. O wiele lepiej jest mieć możliwość zmiany zdania bez zmiany kodu foo . To jest dokładnie przypadek użycia dla połączeń zwrotnych. Musimy jedynie nieznacznie zmienić podpis i foo :

function foo(array, callback) {
    var sum = 0;
    for (var i = 0; i < array.length; i++) {
        callback(array[i]);
        sum += array[i];
    }
    return sum;
}

A teraz jesteśmy w stanie zmienić zachowanie foo po prostu poprzez zmianę jego parametrów:

var array = [];
foo(array, alert);
foo(array, function (x) {
    console.log(x);
});

Przykłady z funkcjami asynchronicznymi

W jQuery metoda $.getJSON() do pobierania danych JSON jest asynchroniczna. Dlatego przekazanie kodu w wywołaniu zwrotnym zapewnia, że kod zostanie wywołany po pobraniu JSON.

Składnia $.getJSON() :

$.getJSON( url, dataObject, successCallback );

Przykład kodu $.getJSON() :

$.getJSON("foo.json", {}, function(data) {
    // data handling code
});

Poniższe nie działałoby, ponieważ kod obsługi danych prawdopodobnie zostałby wywołany przed faktycznym otrzymaniem danych, ponieważ funkcja $.getJSON zajmuje nieokreślony czas i nie wstrzymuje stosu wywołań w oczekiwaniu na JSON.

$.getJSON("foo.json", {});
// data handling code

Innym przykładem funkcji asynchronicznej jest funkcja animate() jQuery. Ponieważ uruchomienie animacji zajmuje określony czas, czasem pożądane jest uruchomienie kodu bezpośrednio po animacji.

.animate() :

jQueryElement.animate( properties, duration, callback );

Na przykład, aby utworzyć animację wygaszania, po której element całkowicie zniknie, można uruchomić następujący kod. Zwróć uwagę na użycie wywołania zwrotnego.

elem.animate( { opacity: 0 }, 5000, function() {
    elem.hide();
} );

Pozwala to ukryć element zaraz po zakończeniu wykonywania funkcji. Różni się to od:

elem.animate( { opacity: 0 }, 5000 );
elem.hide();

ponieważ ten ostatni nie czeka na zakończenie animate() (funkcja asynchroniczna), a zatem element jest od razu ukryty, powodując niepożądany efekt.

Co to jest oddzwanianie?

Jest to normalne wywołanie funkcji:

console.log("Hello World!");

Kiedy wywołujesz normalną funkcję, wykonuje swoją pracę, a następnie zwraca kontrolę z powrotem do osoby dzwoniącej.

Czasami jednak funkcja musi zwrócić kontrolę nad dzwoniącym, aby wykonać swoje zadanie:

[1,2,3].map(function double(x) {
    return 2 * x;
});

W powyższym przykładzie funkcja double jest wywołaniem zwrotnym dla map funkcji, ponieważ:

  1. Funkcja double jest przekazywana do map funkcji przez osobę dzwoniącą.
  2. map funkcji musi wywoływać funkcję double zero lub więcej razy, aby wykonać swoje zadanie.

Tak więc map funkcji w zasadzie zwraca kontrolę do dzwoniącego za każdym razem, gdy wywołuje funkcję double . Stąd nazwa „callback”.


Funkcje mogą akceptować więcej niż jedno połączenie zwrotne:

promise.then(function onFulfilled(value) {
    console.log("Fulfilled with value " + value);
}, function onRejected(reason) {
    console.log("Rejected with reason " + reason);
});

Tutaj funkcja przyjmuje then dwie funkcje zwrotne: onFulfilled i onRejected . Ponadto faktycznie wywoływana jest tylko jedna z tych dwóch funkcji zwrotnych.

Co ciekawsze, funkcja wraca then przed wywołaniem któregoś z wywołań zwrotnych. Dlatego funkcja zwrotna może zostać wywołana nawet po zwróceniu pierwotnej funkcji.

Kontynuacja (synchroniczna i asynchroniczna)

Oddzwaniania można użyć do zapewnienia kodu do wykonania po zakończeniu metody:

/**
 * @arg {Function} then continuation callback
 */
function doSomething(then) {
  console.log('Doing something');
  then();
}

// Do something, then execute callback to log 'done'
doSomething(function () {
  console.log('Done');
});

console.log('Doing something else');

// Outputs:
//   "Doing something"
//   "Done"
//   "Doing something else"

Powyższa metoda doSomething() wykonywana synchronicznie z blokami wywołania zwrotnego do momentu powrotu doSomething() , zapewniając, że wywołanie zwrotne zostanie wykonane przed przejściem interpretera.

Oddzwaniania można również używać do asynchronicznego wykonywania kodu:

doSomethingAsync(then) {
  setTimeout(then, 1000);
  console.log('Doing something asynchronously');
}

doSomethingAsync(function() {
  console.log('Done');
});

console.log('Doing something else');

// Outputs:
//   "Doing something asynchronously"
//   "Doing something else"
//   "Done"

Te then wywołania zwrotne są uważane kontynuacją w doSomething() metod. Zapewnienie wywołania zwrotnego jako ostatniej instrukcji w funkcji nazywa się wywołaniem ogonowym , zoptymalizowanym przez tłumaczy ES2015 .

Obsługa błędów i odgałęzienie sterowania przepływem

Połączenia zwrotne są często używane do obsługi błędów. Jest to forma rozgałęzienia przepływu sterowania, w której niektóre instrukcje są wykonywane tylko w przypadku wystąpienia błędu:

const expected = true;

function compare(actual, success, failure) {
  if (actual === expected) {
    success();
  } else {
    failure();
  }
}

function onSuccess() {
  console.log('Value was expected');
}

function onFailure() {
  console.log('Value was unexpected/exceptional');
}

compare(true, onSuccess, onFailure);
compare(false, onSuccess, onFailure);

// Outputs:
//   "Value was expected"
//   "Value was unexpected/exceptional"

Wykonanie kodu w powyższej funkcji compare() ma dwie możliwe gałęzie: success gdy wartości oczekiwane i rzeczywiste są takie same, i error gdy są różne. Jest to szczególnie przydatne, gdy przepływ sterowania powinien rozgałęzić się po instrukcji asynchronicznej:

function compareAsync(actual, success, failure) {
  setTimeout(function () {
    compare(actual, success, failure)
  }, 1000);
}

compareAsync(true, onSuccess, onFailure);
compareAsync(false, onSuccess, onFailure);
console.log('Doing something else');

// Outputs:
//   "Doing something else"
//   "Value was expected"
//   "Value was unexpected/exceptional"

Należy zauważyć, że wiele wywołań zwrotnych nie musi się wzajemnie wykluczać - można wywoływać obie metody. Podobnie, funkcja Compare compare() może być napisana z opcjonalnymi wywołaniami zwrotnymi (używając domyślnej wartości noop - patrz Wzorzec obiektu zerowego ).

Callbacki i „to”

Często podczas korzystania z oddzwaniania chcesz uzyskać dostęp do określonego kontekstu.

function SomeClass(msg, elem) {
  this.msg = msg;
  elem.addEventListener('click', function() {
    console.log(this.msg);  // <= will fail because "this" is undefined
  });
}

var s = new SomeClass("hello", someElement);

Rozwiązania

  • Użyj bind

    bind skutecznie generuje nową funkcję, która ustawia this na cokolwiek, co zostało przekazane do bind a następnie wywołuje funkcję oryginalną.

      function SomeClass(msg, elem) {
        this.msg = msg;
        elem.addEventListener('click', function() {
          console.log(this.msg);  
        }.bind(this));  // <=-  bind the function to `this`
      }
    
  • Użyj funkcji strzałek

    Strzałka funkcje automatycznie wiążą obecny this kontekst.

      function SomeClass(msg, elem) {
        this.msg = msg;
        elem.addEventListener('click',() => {   // <=-  arrow function binds `this`
          console.log(this.msg);  
        });
      }
    

Często chcesz wywołać funkcję członka, najlepiej przekazując argumenty przekazane do zdarzenia do funkcji.

Rozwiązania:

  • Użyj powiązania

      function SomeClass(msg, elem) {
        this.msg = msg;
        elem.addEventListener('click', this.handleClick.bind(this));
      }
    
      SomeClass.prototype.handleClick = function(event) {
        console.log(event.type, this.msg);
      };
    
  • Użyj funkcji strzałek i operatora reszty

      function SomeClass(msg, elem) {
        this.msg = msg;
        elem.addEventListener('click', (...a) => this.handleClick(...a));
      }
    
      SomeClass.prototype.handleClick = function(event) {
        console.log(event.type, this.msg);
      };
    
  • W szczególności dla detektorów zdarzeń DOM można zaimplementować interfejs EventListener

      function SomeClass(msg, elem) {
        this.msg = msg;
        elem.addEventListener('click', this);
      }
    
      SomeClass.prototype.handleEvent = function(event) {
        var fn = this[event.type];
        if (fn) {
           fn.apply(this, arguments);
        }
      };
      
      SomeClass.prototype.click = function(event) {
        console.log(this.msg);
      };
    

Oddzwonienie za pomocą funkcji strzałki

Użycie funkcji strzałki jako funkcji oddzwaniania może zmniejszyć liczbę wierszy kodu.

Domyślna składnia funkcji strzałki to

() => {}

Może to służyć jako oddzwanianie

Na przykład, jeśli chcemy wydrukować wszystkie elementy w tablicy [1,2,3,4,5]

bez funkcji strzałki kod będzie wyglądał następująco

[1,2,3,4,5].forEach(function(x){
                 console.log(x);
            }

Dzięki funkcji strzałki można ją zmniejszyć do

[1,2,3,4,5].forEach(x => console.log(x));

Tutaj funkcja function(x){console.log(x)} zwrotnej function(x){console.log(x)} jest zredukowana do x=>console.log(x)



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow