javascript

Коллбэк в JavaScript… Что за зверь?

  • четверг, 15 июня 2017 г. в 03:13:20
https://habrahabr.ru/company/ruvds/blog/330880/
  • JavaScript
  • Блог компании RUVDS.com


Если вы не очень хорошо представляете себе — что такое «коллбэки», и как ими пользоваться в JavaScript, сейчас у вас есть шанс их понять и научиться с ними работать.

image

Перейдём сразу к делу. Коллбэк — это функция, которая должна быть выполнена после того, как другая функция завершит работу. Отсюда и название, которое, в английском написании, может быть представлено как «call back», хотя обычно это — «callback». Среди вариантов перевода этого слова — «обратный вызов». В русскоязычных публикациях, допускающих использование жаргона программистов, весьма распространена калька с оригинального названия: «коллбэк». Если же обойтись без жаргона, то о чём мы говорим, называется «функция обратного вызова».

Углубившись, для объяснения сущности функций обратного вызова, в особенности JavaScript, можно сказать, что функции в JS — это объекты. Поэтому функции могут принимать другие функции в качестве аргументов и возвращать их в качестве результатов. Функции, которые работают подобным образом, называют функциями высшего порядка. Коллбэками же обычно называют функции, передаваемые другим функциям в качестве аргументов.

Зачем нужны функции обратного вызова?


Коллбэки нужны по одной очень важной причине: JavaScript — это язык, в котором огромную роль играют события. Это означает, что вместо того, чтобы ожидать, скажем, результата выполнения некоей функции, остановив при этом все остальные операции, JavaScript-программа работает, наблюдая за событиями и реагируя на них.

Взглянем на простой пример:

function first(){
  console.log(1);
}
function second(){
  console.log(2);
}
first();
second();

Как можно ожидать, функция first() выполняется первой, а функция second() второй. Запуск этого кода приводит к тому, что в консоль будет выведено следующее:

// 1
// 2

Пока, надеемся, всё понятно, но что, если функция first() содержит код, который нельзя выполнить немедленно? Например, там есть обращение к некоему API, причём, сначала нужно отправить запрос, а потом дождаться ответа? Для того, чтобы это сымитировать, воспользуемся функцией setTimeout(), которая применяется в JavaScript для вызова других функций с заданной задержкой. Мы собираемся отложить вызов функции на 500 миллисекунд.

Вот что получилось теперь:

function first(){
  // Имитируем задержку
  setTimeout( function(){
    console.log(1);
  }, 500 );
}
function second(){
  console.log(2);
}
first();
second();

Для наших целей особенности работы setTimeout() сейчас неважны. Главное — обратите внимание на то, что вызов console.log(1) будет выполнен с задержкой.

Вот что произойдёт при запуске этого кода:

// 2
// 1

Несмотря на то, что функция first() была вызвана первой, сначала в лог попало то, что выводит функция second().

Это не значит, что JavaScript вызывает функции не в том порядке, в котором мы расположили их вызовы в коде. Смысл в том, что система переходит к исполнению функции second(), не дожидаясь ответа от функции first().

В ситуациях, когда, вызвав некую функцию, нельзя быть уверенным в том, что программа продолжит работу только получив ответ от неё, использование функций обратного вызова — это подход, позволяющий гарантировать то, что некий фрагмент кода будет вызван только после того, как какой-то другой код завершит выполнение. Например, такое постоянно происходит в любой программе, которая так или иначе взаимодействует с внешним миром — скажем, с веб-сервисами.

Создаём функцию обратного вызова


Создадим собственную функцию обратного вызова.

Для начала — откройте консоль разработчика Chrome (Ctrl + Shift + J в Windows, или Cmd + Option + J в Mac) и введите следующее:

function doHomework(subject) {
  alert(`Starting my ${subject} homework.`);
}

Тут мы объявили функцию doHomework(). Эта функция принимает одну переменную — название предмета, по которому некто делает домашнюю работу. Вызовите функцию, введя в консоли следующее:

doHomework('math');
// Выводит сообщение: Starting my math homework.

Теперь добавим, в качестве второго аргумента функции doHomework(), параметр callback, который будем использовать для того, чтобы передать doHomework() функцию обратного вызова. Теперь код будет выглядеть так:

function doHomework(subject, callback) {
  alert(`Starting my ${subject} homework.`);
  callback();
}

Вызовем обновлённую функцию следующими образом:

doHomework('math', function() {
  alert('Finished my homework');
});

Сначала будет выведено сообщение с текстом Starting my math homework., потом — с текстом Finished my homework.

Функции обратного вызова совсем необязательно создавать непосредственно при вызове функций, которым они передаются. Такую функцию можно объявить и где-нибудь в коде:

function doHomework(subject, callback) {
  alert(`Starting my ${subject} homework.`);
  callback();
}
function alertFinished(){
  alert('Finished my homework');
}
doHomework('math', alertFinished);

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

Как вы можете видеть, тут, в качестве аргумента при вызове функции doHomework(), использовано имя функции alertFinished().

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


Для программного взаимодействия с популярной социальной сетью Twitter используется специальное API. Выполняя обращения к этому API, мы вынуждены ждать ответа, и только после его получения можем выполнять с тем, что придёт от Twitter, какие-то действия. Вот материал, где рассмотрена работа с Twitter API в среде Node.js с использованием NPM-пакета twitter.

Рассмотрим фрагмент кода из этого материала. Полагаем, он является отличной демонстрацией практического применения функций обратного вызова.

T.get('search/tweets', params, function(err, data, response) {
  if(!err){
    // Именно здесь можно работать с тем, что вернёт нам Twitter
  } else {
    console.log(err);
  }
})
T.get()

— это функция, которая выполняет get-запрос к Twitter API. У функции три аргумента. Первый — 'search/tweets', представляет собой маршрут запроса. Здесь мы собираемся выполнить поиск по твитам. Второй аргумент — params — это параметры поиска. Третий аргумент — анонимная функция, которая и является функцией обратного вызова.

Функция обратного вызова здесь весьма важна, так как, прежде чем продолжать работу, нужно дождаться ответа от сервера. Неизвестно, будет ли обращение к API успешным, поэтому, после отправки параметров поиска по маршруту search/tweet с помощью get-запроса, приходится ждать. Как только Twitter ответит на запрос, будет выполнена функция обратного вызова. Если что-то пошло не так, в ней мы получим объект ошибок (err). Если запрос обработан нормально, в аргументе err будет значение, эквивалентное false, а значит, во-первых, будет исполнена ветвь if условного оператора, а во-вторых — можно будет рассчитывать на то, что в объекте response окажутся некие полезные данные, с которыми уже можно что-то делать.

Итоги


Надеемся, наш рассказ о функциях обратного вызова в JavaScript оказался полезен тем, кто не очень хорошо в них разбирался. На самом деле, то, о чём мы здесь говорили — это лишь вершина айсберга. Однако теперь, поняв основы, вы можете расширять и углублять свои знания в этой области.

Уважаемые читатели! Если вы из тех, кто, до чтения этого материала, плохо представлял себе, что такое функции обратного вызова в JS, скажите — стало понятнее? А если коллбэки для вас — обычное дело, просим поделиться опытом с новичками.