Что такое колл бэк
Понимание callback-функций (колбеков)
Функции — это объекты
Чтобы понимать callback-функции, нужно понимать обычные функции. Это может показаться банальностью, но функции в Javascript’е — немного странные штуки.
Преимущество концепции «функция-как-объект» заключается в том, что код можно передавать в другую функцию точно так же, как обычную переменную или объект (потому что в буквальном понимании код — всего лишь объект).
Передача функции как callback-функции
Передавать функцию в качестве аргумента просто.
Может показаться глупостью создавать такой перемудрённый код, когда можно вернуть значение нормальным способом, но существуют ситуации, в которых это непрактично и callback-функции необходимы.
Не загромождайте выход
Традиционно функции в ходе выполнения принимают на вход аргументы и возвращают значение, используя выражение return (в идеальном случае единственное выражение return в конце функции: одна точка входа и одна точка выхода). Это имеет смысл. Функции — это, в сущности, маршруты между вводом и выводом.
Javascript даёт возможность делать всё немного по-другому. Вместо того чтобы дожидаться, пока функция закончит выполняться и вернёт значение, мы можем использовать callback-функции, чтобы получить его асинхронно. Это полезно для случаев, когда требуется много времени для завершения, например, при AJAX-запросах, ведь мы не можем приостановить браузер. Мы можем продолжить заниматься другими делами в ожидании вызова колбека. Фактически, очень часто от нас требуется (или, точнее, нам настоятельно рекомендуется) делать всё асинхронно в Javascript’е.
Вот более детальный пример, в котором используется AJAX для загрузки XML-файла и используется функция call() для вызова callback-функции в контексте запрошенного объекта (это значит, что когда мы укажем ключевое слово this внутри callback-функции, оно будет ссылаться на запрошенный объект):
В этом примере мы создаём объект httpRequest и загружаем файл XML. Типичная парадигма возвращения значения в конце функции тут больше не работает. Наш запрос обрабатывается асинхронно, а это означает, что мы начинаем запрос и говорим ему вызвать нашу функцию, как только он закончится.
Мы используем здесь две анонимных функции. Важно помнить, что нам бы не составило труда использовать и именованные функции, но во имя лаконичности мы сделали их вложенными. Первая анонимная функция выполняется всякий раз при изменении статуса в нашем объекте httpRequest. Мы игнорируем это до тех пор, пока состояние не будет равно 4 (т.е. запрос выполнен) и статус будет равен 200 (т.е. запрос выполнен успешно). В реальном мире вам бы захотелось проверить, не провален ли запрос, но мы предполагаем, что файл существует и может быть загружен браузером. Эта анонимная функция связана с httpRequest.onreadystatechange, так что она выполняется не сразу, а вызывается каждый раз при изменении состояния в нашем запросе.
Коллбэк в JavaScript… Что за зверь?
Если вы не очень хорошо представляете себе — что такое «коллбэки», и как ими пользоваться в JavaScript, сейчас у вас есть шанс их понять и научиться с ними работать.
Перейдём сразу к делу. Коллбэк — это функция, которая должна быть выполнена после того, как другая функция завершит работу. Отсюда и название, которое, в английском написании, может быть представлено как «call back», хотя обычно это — «callback». Среди вариантов перевода этого слова — «обратный вызов». В русскоязычных публикациях, допускающих использование жаргона программистов, весьма распространена калька с оригинального названия: «коллбэк». Если же обойтись без жаргона, то о чём мы говорим, называется «функция обратного вызова».
Углубившись, для объяснения сущности функций обратного вызова, в особенности JavaScript, можно сказать, что функции в JS — это объекты. Поэтому функции могут принимать другие функции в качестве аргументов и возвращать их в качестве результатов. Функции, которые работают подобным образом, называют функциями высшего порядка. Коллбэками же обычно называют функции, передаваемые другим функциям в качестве аргументов.
Зачем нужны функции обратного вызова?
Коллбэки нужны по одной очень важной причине: JavaScript — это язык, в котором огромную роль играют события. Это означает, что вместо того, чтобы ожидать, скажем, результата выполнения некоей функции, остановив при этом все остальные операции, JavaScript-программа работает, наблюдая за событиями и реагируя на них.
Взглянем на простой пример:
Как можно ожидать, функция first() выполняется первой, а функция second() — второй. Запуск этого кода приводит к тому, что в консоль будет выведено следующее:
Вот что получилось теперь:
Для наших целей особенности работы setTimeout() сейчас неважны. Главное — обратите внимание на то, что вызов console.log(1) будет выполнен с задержкой.
Вот что произойдёт при запуске этого кода:
В ситуациях, когда, вызвав некую функцию, нельзя быть уверенным в том, что программа продолжит работу только получив ответ от неё, использование функций обратного вызова — это подход, позволяющий гарантировать то, что некий фрагмент кода будет вызван только после того, как какой-то другой код завершит выполнение. Например, такое постоянно происходит в любой программе, которая так или иначе взаимодействует с внешним миром — скажем, с веб-сервисами.
Создаём функцию обратного вызова
Создадим собственную функцию обратного вызова.
Для начала — откройте консоль разработчика Chrome ( Ctrl + Shift + J в Windows, или Cmd + Option + J в Mac) и введите следующее:
Вызовем обновлённую функцию следующими образом:
Функции обратного вызова совсем необязательно создавать непосредственно при вызове функций, которым они передаются. Такую функцию можно объявить и где-нибудь в коде:
После вызова функции doHomework() всё будет выглядеть точно так же, как в предыдущем примере. Различия заключаются лишь в том, как мы работаем с функцией обратного вызова.
Функции обратного вызова в реальных проектах
Для программного взаимодействия с популярной социальной сетью Twitter используется специальное API. Выполняя обращения к этому API, мы вынуждены ждать ответа, и только после его получения можем выполнять с тем, что придёт от Twitter, какие-то действия. Вот материал, где рассмотрена работа с Twitter API в среде Node.js с использованием NPM-пакета twitter.
Рассмотрим фрагмент кода из этого материала. Полагаем, он является отличной демонстрацией практического применения функций обратного вызова.
Итоги
Надеемся, наш рассказ о функциях обратного вызова в JavaScript оказался полезен тем, кто не очень хорошо в них разбирался. На самом деле, то, о чём мы здесь говорили — это лишь вершина айсберга. Однако теперь, поняв основы, вы можете расширять и углублять свои знания в этой области.
Уважаемые читатели! Если вы из тех, кто, до чтения этого материала, плохо представлял себе, что такое функции обратного вызова в JS, скажите — стало понятнее? А если коллбэки для вас — обычное дело, просим поделиться опытом с новичками.
Что такое callback-функция в JavaScript?
Что такое коллбэк?
Простыми словами: коллбэк — это функция, которая должна быть выполнена после того, как другая функция завершила выполнение (отсюда и название: callback – функция обратного вызова).
Чуть сложнее: В JavaScript функции — это объекты. Поэтому функции могут принимать другие функции в качестве аргументов, а также функции могут возвращать функции в качестве результата. Функции, которые это умеют, называются функциями высшего порядка. А любая функция, которая передается как аргумент, называется callback-функцией. Чтобы лучше разобраться, давайте посмотрим на примерах, как это выглядит.
Зачем нам нужны коллбэки?
По одной простой причине: JavaScript — это событийно-ориентированный язык. Это значит, что вместо того, чтобы ждать ответа для дальнейшего выполнения программы, JavaScript продолжит выполнение, одновременно ожидая других событий. Давайте разберем простой пример:
Как вы и ожидаете, функция first выполнится первой, а функция second после нее, и в консоли будет выведен следующий результат:
Зачем я вам это показал? Чтобы вы понимали, нельзя просто вызывать функции в нужном порядке и надеяться, что они в любом случае выполнятся в том же порядке. Коллбэки же позволяют нам быть уверенными в том, что определенный код не начнет исполнение до того момента, пока другой код не завершит исполнение.
Создаем коллбэк
Хватит болтовни, теперь давайте создадим коллбэк.
Во-первых, откройте консоль разработчика в Google Chrome (Windows: Ctrl + Shift + J)(Mac: Cmd + Option + J) и введите в консоли следующую функцию:
Теперь давайте добавим в определение функции еще один параметр, это и будет наш коллбэк. Затем вызовем ее, определив функцию-callback в качестве аргумента:
Если вы введете этот код в консоли, вы получите два алерта один за другим, в первом будет сообщение о том, что выполнение домашнего задания началось (Starting my math homework.), а во втором — что вы закончили выполнять задание (Finished my homework).
Однако коллбэки не обязательно должны быть определены при вызове функции. Они могут быть определены и в другом месте кода, например, так:
Таким образом, результат выполнения этого кода такой же, как и в предыдущем примере, однако сам код немного другой. Как вы видите, мы передали функцию alertFinished как аргумент в функцию doHomework при ее вызове.
Пример из реальной жизни
На прошлой неделе я опубликовал статью «Создаем бота для Твиттера в 38 строк кода». Этот код работает благодаря API Твиттера. И когда мы делаем запрос к API, мы должны дождаться ответа до того, как начнем выполнять с этим ответом какие-то действия. Это прекрасный пример того, как в реальной жизни выглядит коллбэк. Вот как выглядит сам запрос:
T.get просто значит, что мы выполняем get запрос к API Твиттера. В запросе три параметра: ‘search/tweets’ – это адрес (роут) запроса, params – наши параметры поиска и в конце передается анонимная функция-callback.
Коллбэк здесь нужен, потому что нам нужно дождаться ответа от сервера до того, как приступим к дальнейшему выполнению кода. Мы не знаем, успешным будет наш запрос или нет, поэтому после отправки параметров поиска на search/tweets через get-запрос, мы просто ждем. Как только Твиттер ответит, выполнится наша callback-функция. Твиттер отправит нам в качестве ответа или объект err (error – ошибка), или объект response. В коллбэке мы можем через if() проверить, был ли запрос успешным или нет, и затем действовать соответственно.
Никогда не останавливайтесь: В программировании говорят, что нужно постоянно учиться даже для того, чтобы просто находиться на месте. Развивайтесь с нами — на Хекслете есть сотни курсов по разработке на разных языках и технологиях
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Введение в колбэк-функции в JavaScript
У этой статьи есть продолжение — там мы рассказываем про работу с сервером и передачу параметров в коллбэк-функции.
При изучении программирования мы привыкаем мыслить последовательно: строки кода выполняются по порядку. Для многих языков это утверждение верно на 100%, но всё начинает меняться, когда речь заходит про асинхронное программирование.
Это статья о том, как работают и зачем нужны колбэк-функции в JavaScript. Это одна из важнейших тем программирования на JavaScript, и ни одна более-менее серьёзная программа не обойдется без применения колбэк-функций.
Что такое колбэк-функция
Колбэк-функция или функция обратного вызова — функция, предназначенная для отложенного выполнения. Проще говоря, она должна быть выполнена после завершения работы другой функции. Чтобы стало понятнее, разберём пример с заказом пиццы.
Колбэк (callback) переводится как «Перезвоните». Действительно, принцип работы колбэков схож с заказом обратного телефонного звонка. Представьте, что вы звоните оператору для заказа пиццы, но срабатывает автоответчик, где приятный голос просит оставаться на линии, пока не освободится оператор, или предлагает заказать обратный звонок. Когда оператор освободится — он перезвонит и примет заказ.
Это прекрасная аналогия для понимания принципов работы колбэков и асинхронности. Вместо ожидания ответа оператора, мы можем заказать обратный звонок и заниматься другими делами. Как только произойдёт колбэк (нам перезвонили), мы сможем выполнить задуманное — заказать пиццу.
Как писать код для колбэков
Посмотрим на колбэки с практической стороны.
Выше мы сказали, что колбэки неразрывно связаны с асинхронностью и позволяют «запланировать» действие, которое будет совершено после выполнения какого-то другого, возможно длительного действия. Пример с заказом пиццы это прекрасно илюстрирует. Давайте посмотрим, как это может выглядеть в коде, но для начала взглянем на синхронный код:
Что в этом коде больше всего бросается в глаза? Правильно — последовательность. Здесь представлен синхронный код, который будет выполнятся последовательно:
Проблема видна невооруженным глазом — пока готовится пицца, мы вынуждены ждать и ничего не делать. Строка readBook() будет выполнена только после приготовления пиццы. Фактически мы начнём читать книгу после приготовления пиццы, а не во время готовки.
Само собой, в реальном мире вместо выпекания пиццы может быть любой долгий процесс, например, запрос на получение данных с сервера.
Такой запрос не выполняется мгновенно: браузеру понадобится время, чтобы найти IP-адрес сервера, установить соединение, передать запрос, дождаться ответа и т.д. Все эти действия занимают разное количество времени. Временные задержки будут постоянно отличаться и зависеть от скорости соединения с сетью, времени выполнения запроса на сервере и некоторых других факторов.
Синхронные запросы к серверу будут блокировать дальнейшее выполнение веб-приложения, и это уже очень плохо. Представьте, что каждый раз при отправке запроса к серверу интерфейс вашего приложения становится полностью недоступным.
Эту проблему решает асинхронность, и длительные операции лучше выполнять именно асинхронно. В этом варианте мы как бы откладываем длительную операцию «на потом» и вместо ожидания завершения выполняем другой код. В этой схеме прозрачно всё, кроме вопроса: «Как выполнить код после завершения асинхронной операции?». Ответ прост — функции обратного вызова.
В JavaScript функции являются объектами высшего порядка. Это означает, что функции можно передавать в другие функции в виде параметров или возвращать в виде результата выполнения.
В первом случае мы вызываем функцию foo при помощи круглых скобок и выводим результат выполнения в консоль. Во втором примере мы не делаем вызов функции (обратите внимание на отсутствие круглых скобок), и в консоль выводится содержимое функции. Выходит, нам ничего не мешает передать функцию в виде параметра для других функций:
Что в итоге? Мы передали ссылку на функцию в виде параметра и вызвали её внутри другой функции. В этом и заключается идея колбэков: мы передаем в виде параметров функции, которые будут вызваны «когда-нибудь потом».
И снова пицца
Вернёмся к примеру с приготовлением пиццы. Попробуем поэкспериментировать с кодом и перевести его на асинхронные рельсы. Напомню, наша задача — попросить приготовить пиццу, и читать книгу, пока пицца не будет готова.
Это рабочий код, попробуйте выполнить его в консоли и посмотреть на результат вывода. Он будет таким:
Как видите, ничего сверхъестественного в колбэках нет. Это обычная функция, которая будет выполнена не сейчас, а когда-нибудь потом. «Когда-нибудь» — не преувеличение. Мы не можем сказать, в какой момент времени это случится, но можем сказать, после какой именно функции — после выполнения функции приготовлении пиццы.
Делегаты и колбэки в Swift простым языком. Что же такое этот delegate, и как работает callback
В Swift при изучении UI (User Interface) каждый рано или поздно приходит к необходимости использования делегата. Все гайды о них пишут, и вроде бы делаешь, как там написано, и вроде бы работает, но почему и как это работает, не у каждого в голове укладывается до конца. Лично у меня даже какое-то время складывалось ощущение, что delegate – это какое-то волшебное слово, и что оно прям встроено в язык программирования (вот, насколько запутаны были мои мысли от этих гайдов). Давайте попытаемся объяснить простым языком, что же это такое. А разобравшись с делегатом, уже гораздо легче будет понять, что такое колбэк (callback), и как работает он.
Официант и повар
Итак, перед тем как перейти к коду давайте представим себе некоего официанта и какого-нибудь повара. Официант получил заказ от клиента за столиком, но сам он готовить не умеет, и ему нужно попросить об этом повара. Он может пойти на кухню и сказать повару: «Приготовь курицу». У повара есть соответствующие инструменты (сковорода, масло, огонь…) и навык приготовления. Повар готовит и отдает блюдо официанту. Официант берет то, что сделано поваром и несет к клиенту.
А теперь представим себе ситуацию, что официант не может прибежать на кухню и сказать повару напрямую, какое блюдо у него заказали. Не пускают его на кухню (допустим, такие правила) или кухня находится на другом этаже (устанешь бегать). И единственный способ общения – это окошко мини-лифта. Официант кладет туда записку, нажимает кнопку, лифт уехал на кухню. Приезжает обратно с готовым блюдом. Запомнили? Теперь зафиксируем ситуацию в голове, попробуем воссоздать через код и понять, как это связано с нашей темой.
Перенесем в код
Создаем классы официанта и повара. Для простоты сделаем это в плейграунде:
Теперь создаем их экземпляры (нанимаем на работу), и просим официанта получить заказ (курицу):
Как теперь официанту передать повару, что ему приготовить?
Конечно, можно было бы написать ниже вот так, если бы свойства и методы официанта и повара не были private. Но так как у них уровень доступа private, то это не сработает:
И к тому же мы использовали некий сторонний код «снаружи» классов официанта и повара, которому необходимо иметь доступ к private свойствам и методам этих классов. А как официанту передать «изнутри» себя, используя встроенные свойства и методы своего класса? Тот же вопрос со стороны повара: «Как ему приготовить то, что известно только официанту, используя свойства и методы своего класса?»
Тут на помощь приходит «лифт». В этот лифт официант кладет записку с заказом. А повар берет записку из лифта и ставит в лифт готовое блюдо для передачи повару. Такой «лифт» реализуется через протокол «Взаимообмен через лифт»:
В данном случае мы говорим, что у лифта есть «интерфейс», который должен быть понятен и официанту, и повару. То есть правила взаимообмена через этот лифт. Правила взаимообмена должны знать и официант, и повар. Они простые: официант кладет записку, а повар готовит по ней.
Теперь давайте поправим классы, чтобы официант мог общаться с поваром через этот лифт. Научим их работать по этому правилу(протоколу).
Подпишем класс повара под протокол лифта. Грубо говоря, мы научим всех наших поваров соблюдать правила, описанные в этом протоколе «Обмен через лифт». В таком случае Xcode заставит нас дописать в класс повара метод из протокола. Этот метод в данном примере должен будет вернуть Bool значение.
В этом методе мы вызовем ранее созданный метод cookFood, который повар умеет выполнять.
Далее официанту добавим свойство «получатель заказа через лифт». Официант знает, что этот получатель знает правила и приготовит то, что в записке.
В данном примере мы использовали расширение, которое не позволяет делать переменные с хранимыми свойствами. Поэтому мы вынуждены дописать в фигурных скобках return cook.
Но можно сделать по-другому: просто добавить без расширения прямо внутрь класса официанта это опциональное свойство, а затем извне этому свойству присвоить экземпляр нашего повара. Главное не забыть подписать класс повара под протокол.
Давайте так и сделаем, чтобы не усложнять наш простой пример экстеншенами. Удалим эти экстешнены и просто добавим строчки в ранее созданные классы, и подпишем класс повара под протокол.
Вот теперь как будут выглядеть наши классы:
В классе официанта убрали пока private у свойства order (нужно сейчас для наглядности).
Далее все по той же схеме:
Теперь скажем официанту, что его «получатель заказа через лифт» – это наш повар.
Как уже говорилось ранее, официант знает, что этот получатель знает правила и приготовит то, что в записке.
Теперь официант может нашего «получателя заказа через лифт» попросить приготовить заказ:
Запускаем код, получаем результат!
Как Дубровский «имел связь с Машей через дупло», так и наш официант теперь имеет возможность отправлять заказы повару через протокол и известный официанту метод данного протокола.
Можно теперь доделать у класса «Официант» метод передачи заказа, чтобы официант мог делать это «изнутри себя», то есть используя свои собственные свойства и методы, а не указания со стороны.
Вот и все дела! В данном случае мы добавляли официанту опциональное свойство receiverOfOrderViaElevator, подписанное под протокол. Это свойство и есть делегат. Можете заменить название этого свойства на delegate, если хотите. По сути ничего не изменится, просто это более универсальное слово, вот и все.
Теперь вы понимаете принцип работы делегата? «Окей, – скажете вы. – А как это использовать в UI?»
Как использовать delegate при создании контроллеров в UI?
В UI с необходимостью использования делегата чаще всего встречаются в случае, когда необходимо от одного «дочернего» контроллера передать информацию «родительскому». Например, нужно передать от ячейки информацию создавшему ее table view или collection view. От table view или collection view передать информацию ячейке легко: это можно сделать при ее инициализации. А вот обратно передать информацию сама ячейка напрямую не может. Вот тут и спасает шаблон (паттерн) под названием «Делегат» («Delegate»).
Кстати, слово Delegable в русской раскладке будет как «Вудупфиду». Так вот, значит, о чем пела Мерлин Монро!
Давайте представим, что повар – это хозяин кафе. И он нанимает нашего официанта. То есть создает экземпляр класса Waiter. Добавим ему умение(метод) hireWaiter. Получим вот такой класс (кстати, пусть на этот раз это будет шеф-повар):
Теперь шеф-повар открывает кафе и нанимает официанта (создаем экземпляр шеф-повара и вызываем у него метод hireWaiter):
Далее все по старинке. Нужно обучить официанта, что его «получатель заказа через лифт» – это наш шеф-повар. И тогда он сможет передать ему заказ.
Если запустить код, то снова все сработает.
Отлично. А теперь представим, что появился новый шеф-повар, который рассказывает официанту ещё при найме на работу, что его «получателем заказа через лифт» будет сам шеф-повар.
Здесь мы просто наследовали класс SmartChief от класса Chief и переписали метод найма официантов.
Получается, что теперь нет необходимости как-то отдельно указывать официанту (обучать), кто его получатель заказа через лифт. Он с самого начала работы уже об этом знает!
Тоже самое и в контроллерах:
Естественно, это можно делать не только с «порождающими» контроллерами, а вообще с любыми объектами, которые мы хотим научить общаться друг с другом.
На этом про делегаты всё. Надеюсь, было полезно! Теперь разберемся в колбэках.
Колбэки (колбеки, callback). Это тот же делегат? Ну, почти
Итак, мы выяснили, что делегаты позволяют одним объектам «общаться» с другими. Иначе говоря, позволяют одним объектам добиваться своей цели с помощью свойств и методов других объектов. Для чего же нужны колбэки? И почему они в одной статье с делегатами?
Колбэк (callback) – это тоже шаблон программирования, как и делегат. С его помощью также одни объекты могут добиваться своих целей с помощью свойств и методов других объектов. Только делать они это будут сами.
Ленивый шеф-повар, талантливый официант
Представьте себе, что в другом ресторане шеф-повар очень ленивый (или усталый, или заболел). Официант принял заказ, звонит повару, а тот ему говорит: «Давай-ка сам приготовь! Возьми мою сковородку, поставь на плиту и приготовь по рецепту!» Наш официант оказался не из робкого десятка, берет инструкцию, инструменты повара и все делает сам. Это и есть подход через колбэк.
Давайте посмотрим это на практике.
Создадим класс талантливого официанта. Добавим опциональное свойство функционального типа. Под этим названием кроется функция, которая принимает на вход аргумент с типом String и возвращает результат с типом Bool. Прямо как метод cookFood у нашего повара! Это что-то вроде способности сделать что-то по инструкции.
Далее создаем класс ленивого шеф-повара. В этот раз при найме на работу талантливого официанта, повар не назначает себя делегатом, а обучает его самому готовить. Он передает в именованное функциональное свойство официанта свое умение готовить, которое включает в себя и доступ к сковородке с плитой. То есть он присваивает замыкание его свойству, и в этом замыкании передает свой собственный метод:
В результате после появления такого шеф-повара, найме на работу официанта, официант готовит блюдо самостоятельно, как только примет заказ:
Таким образом, как и в случае с делегатом, официант «добился» своей цели и приготовил заказ.
Получается, что способ взаимосвязи через колбэк выглядит даже несколько короче, чем способ через делегат. По крайней мере, не нужно заморачиваться с протоколами, строить правила (лифты).
Однако у этого способа есть один минус – создание сильных связей. Поэтому нужно всегда помнить, что self нужно передавать через слабую ссылку, иначе экземпляр шеф-повара навсегда свяжется с экземпляром официанта, что приведет к утечке памяти даже после удаления этих объектов.
Поэтому последний штрих будет добавить [weak self] перед in и аргументами в передающем замыкании. Всегда помните об этом, пожалуйста!
На этом у меня все. Надеюсь, вам было полезно. Успехов в изучении!