Поиск…
Примеры использования простого обратного вызова
Обратные вызовы предлагают способ расширения функциональности функции (или метода) без изменения ее кода. Этот подход часто используется в модулях (библиотеки / плагины), код которых не предполагается изменять.
Предположим, что мы написали следующую функцию, вычисляющую сумму заданного массива значений:
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
функций, потому что:
- Функция
double
присваивается функциональнойmap
вызывающим. - Функция
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)