Callbacks

Callbacks (или обратные функции) — это на самом деле не опция JavaScript, как Object (объект) или Array (массив), а просто определенный способ использования функций.

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

Асинхронный код по определению — это код, написанный не синхронно. Синхронный код легко понять и написать. Вот пример для иллюстрации:

var photo = download('http://foo-chan.com/images/sp.jpg')
uploadPhotoTweet(photo, '@maxogden')

Этот синхронный псевдокод скачивает очаровательную фотографию кота, а затем загружает фотографию в твиттер и отправляет ее на @maxogden. Довольно просто!

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

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

А затем, когда скачивание завершится, будет разблокировано выполнение JavaScript, и строка 2 будет выполнена.

Синхронный код хорош для вещей, которые происходят быстро, но ужасен для вещей, которые требуют сохранения, загрузки, скачивания или выгрузки.

Что если сервер, с которого вы загружаете фотографию, работает медленно, или интернет-соединение, которым вы пользуетесь, работает медленно, или на компьютере, на котором вы запускаете код, слишком много открытых вкладок с youtube и он работает медленно?

Это означает, что потенциально может занять несколько минут ожидания, прежде чем строка 2 приступит к работе. Между тем, поскольку весь JavaScript на странице блокируется от запуска во время загрузки, веб-страница полностью зависает и перестает отвечать до завершения скачивания.

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

Чтобы проиллюстрировать, сколько времени занимает одна секунда для современного компьютера, вот программа, которая проверяет, сколько задач JavaScript может обработать за одну секунду.

function measureLoopSpeed() {
  var count = 0
  function addOne() { count = count + 1 }

  // Date.now() returns a big number representing the number of
  // milliseconds that have elapsed since Jan 01 1970
  var now = Date.now()

  // Loop until Date.now() is 1000 milliseconds (1 second) or more into
  // the future from when we started looping. On each loop, call addOne
  while (Date.now() - now < 1000) addOne() // Finally it has been >= 1000ms, so let's print out our total count
  console.log(count)
}

measureLoopSpeed()

Скопируйте и вставьте приведенный выше код в консоль JavaScript, и через секунду он должен вывести число. На моем компьютере я получил 8527360, примерно 8,5 миллионов.

За одну секунду JavaScript может вызвать функцию addOne 8,5 миллионов раз! Таким образом, если у вас есть синхронный код для скачивания фотографии, а загрузка фотографии занимает одну секунду, это означает, что вы потенциально предотвращаете выполнение 8,5 миллионов операций, когда выполнение JavaScript заблокировано.

В некоторых языках есть функция sleep, которая блокирует выполнение на некоторое количество секунд. Например, вот некоторый код bash, работающий в Terminal.app в Mac OS, который использует sleep.

Когда вы запускаете команду sleep 3 && echo 'done sleep now', она блокируется на 3 секунды, прежде чем выведет 'done sleep now'.

Callbacks

У JavaScript нет функции sleep (сна). Вероятно, вы спрашиваете себя: «Почему я изучаю язык программирования, который не подразумевает сон?».

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

Например, это код в стиле блокировки:

a()
b()

А этом в неблокирующем стиле:

a(b)

В неблокирующей версии b это обратный вызов a. В блокирующей версии a и b оба вызываются (они обе имеют () после них, что немедленно выполняет функцию). В неблокирующей версии вы заметите, что вызывается только a, а b просто передается в качестве аргумента.

В версии блокировки нет явной связи между a и b. В неблокирующей версии это становится задачей a делать то, что нужно, а затем вызывать b, когда это будет сделано.

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

Вот псевдокод реализации того, как a может выглядеть:

function a(done) {
  download('https://pbs.twimg.com/media/B4DDWBrCEAA8u4O.jpg:large', function doneDownloading(error, png) {
    // handle error if there was one
    if (err) console.log('uh-oh!', error)

    // call done when you are all done
    done()
  })
}

Вспомните наш неблокирующий пример a(b), где мы вызываем a и передаем b в качестве первого аргумента. В определении функции для a над done наша функция b, которую мы передаем. Такое поведение сначала сложно понять.

Когда вы вызываете функцию a, передаваемые аргументы не будут иметь одинаковые имена переменных, когда они находятся в функции. В этом случае то, что мы называем b, называется done внутри функции.

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

Таким образом, до тех пор, пока a выполняет свою работу и вызывает b по завершении, a и b будут вызываться как в неблокирующей, так и в блокирующей версиях.

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

Если a занимает одну секунду и вы используете блокирующую версию, это означает, что вы можете выполнить только одну задачу.

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

Помните: программирование — это про лень, и вы должны спать, а не сидеть за компьютером.

Надеюсь, теперь вы увидели, что обратные функции — это просто функции, которые вызывают другие функции после некоторой асинхронной задачи.

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

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

Была ли эта страница полезной?