Поиск…


Примеры использования простого обратного вызова

Обратные вызовы предлагают способ расширения функциональности функции (или метода) без изменения ее кода. Этот подход часто используется в модулях (библиотеки / плагины), код которых не предполагается изменять.

Предположим, что мы написали следующую функцию, вычисляющую сумму заданного массива значений:

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

Теперь предположим, что мы хотим что-то сделать с каждым значением массива, например, отображать его с помощью alert() . Мы могли бы внести соответствующие изменения в код foo , например:

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

Но что, если мы решили использовать console.log вместо alert() ? Очевидно, что изменение кода foo , когда мы решаем сделать что-то еще с каждым значением, не является хорошей идеей. Намного лучше иметь возможность изменить наш разум, не изменяя код foo . Это именно тот вариант использования обратных вызовов. Нам нужно лишь слегка изменить подпись и тело foo :

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

И теперь мы можем изменить поведение foo просто изменив его параметры:

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

Примеры с асинхронными функциями

В jQuery метод $.getJSON() для извлечения данных JSON является асинхронным. Поэтому передача кода в обратном вызове гарантирует, что код вызывается после того, как JSON будет извлечен.

$.getJSON() :

$.getJSON( url, dataObject, successCallback );

Пример кода $.getJSON() :

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

Следующие действия не будут работать, поскольку код обработки данных, скорее всего, будет вызываться до того, как данные будут фактически получены, поскольку функция $.getJSON занимает неопределенный промежуток времени и не удерживает стек вызовов, так как ожидает JSON.

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

Другим примером асинхронной функции является функция animate animate() jQuery. Поскольку для запуска анимации требуется определенное время, иногда желательно запустить некоторый код непосредственно после анимации.

.animate() :

jQueryElement.animate( properties, duration, callback );

Например, чтобы создать анимацию замирания, после которой элемент полностью исчезнет, ​​можно запустить следующий код. Обратите внимание на использование обратного вызова.

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

Это позволяет скрывать элемент сразу после завершения выполнения функции. Это отличается от:

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

потому что последний не ждет завершения animate() (асинхронной функции), и поэтому элемент сразу скрывается, что создает нежелательный эффект.

Что такое обратный вызов?

Это обычный вызов функции:

console.log("Hello World!");

Когда вы вызываете обычную функцию, она выполняет свою работу, а затем возвращает управление вызывающему.

Однако иногда функция должна возвращать управление обратно вызывающему абоненту для выполнения своей работы:

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

В приведенном выше примере функция double является обратным вызовом для map функций, потому что:

  1. Функция double присваивается функциональной map вызывающим.
  2. Функция map необходимо вызвать функцию double ноль или более раз для того , чтобы сделать свою работу.

Таким образом, функциональная map по существу возвращает управление обратно вызывающему абоненту каждый раз, когда он вызывает функцию double . Следовательно, имя «обратный вызов».


Функции могут принимать более одного обратного вызова:

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

Затем функция then принимает две функции обратного вызова, onFulfilled и onRejected . Кроме того, на самом деле называется только одна из этих двух функций обратного вызова.

Что более интересно , что функция then возвращает , прежде чем либо из обратных вызовов называется. Следовательно, функция обратного вызова может быть вызвана даже после возвращения исходной функции.

Продолжение (синхронно и асинхронно)

Обратные вызовы могут использоваться для предоставления кода, который должен быть выполнен после завершения метода:

/**
 * @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"

Метод doSomething() выше выполняет синхронно с блоками doSomething() до doSomething() пор, пока doSomething() вернется, гарантируя, что обратный вызов будет выполнен до того, как интерпретатор перейдет.

Обратные вызовы также могут использоваться для асинхронного выполнения кода:

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"

then обратные вызовы считаются продолжениями методов doSomething() . Предоставление обратного вызова в качестве последней команды в функции называется хвостовым вызовом , который оптимизируется с помощью интерпретаторов ES2015 .

Обработка ошибок и ветвление ветвей управления

Обратные вызовы часто используются для обеспечения обработки ошибок. Это форма ветвления ветвей управления, где некоторые команды выполняются только при возникновении ошибки:

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"

Исполнение кода в compare() выше имеет две возможные ветви: success когда ожидаемые и фактические значения одинаковы, и error когда они различны. Это особенно полезно, когда поток управления должен входить после некоторой асинхронной команды:

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"

Следует отметить, что множественные обратные вызовы не обязательно должны быть взаимоисключающими - оба метода могут быть вызваны. Аналогично, compare() можно записать с помощью обратных вызовов, которые являются необязательными (с использованием noop в качестве значения по умолчанию - см. Шаблон Null Object ).

Обратные вызовы и `this`

Часто при использовании обратного вызова вы хотите получить доступ к определенному контексту.

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

Решения

  • Использовать bind

    bind эффективно генерирует новую функцию, которая устанавливает this на все, что было передано для bind затем вызывает исходную функцию.

      function SomeClass(msg, elem) {
        this.msg = msg;
        elem.addEventListener('click', function() {
          console.log(this.msg);  
        }.bind(this));  // <=-  bind the function to `this`
      }
    
  • Использовать функции стрелок

    Функции стрелки автоматически связать текущую this контекст.

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

Часто вы хотите вызвать функцию-член, в идеале передающую любые аргументы, переданные событию на функцию.

Решения:

  • Использовать bind

      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);
      };
    
  • Используйте функции стрелок и оператор останова

      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);
      };
    
  • Для прослушивателей событий DOM вы можете реализовать интерфейс 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);
      };
    

Обратный вызов с использованием функции стрелки

Использование функции стрелки в качестве функции обратного вызова может уменьшить количество строк кода.

Синтаксис по умолчанию для функции стрелок

() => {}

Это можно использовать как обратные вызовы

Например, если мы хотим напечатать все элементы в массиве [1,2,3,4,5]

без функции стрелки, код будет выглядеть следующим образом:

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

С функцией стрелки ее можно уменьшить до

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

Здесь функция function(x){console.log(x)} обратного вызова function(x){console.log(x)} сводится к x=>console.log(x)



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow