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

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

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

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

Во-вторых, я буду использовать ES6, когда буду рассматривать примеры кода — я предоставил некоторые функции ES5 в качестве ссылки, но во всех моих сообщениях стрелочные функции ES6 останутся предпочтительным синтаксисом, и я буду использовать стиль ES5 только при необходимости ( например, функции, требующие команды this или объекта arguments)

Функции высшего порядка

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

  • Называйтесь переменными.
  • Передаются в функции в качестве аргументов.
  • Возвращаться как значения функций.
  • Быть значением выражения.
  • Быть членами списка/кортежа.

Энтони Дж. Т. Дэви (1992)

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

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

Например, возьмите следующие функции

ES5

function introduce(firstName, lastName, callback) {
    var fullName = firstName + ' ' +  lastName
    console.log('Hello ' +  firstName + ' '+ lastName) 
    callback(fullName) 
}
function greetings (name) {
  console.log('It\'s a pleasure to meet you ' + name)
}
introduce('Identity', 'Crisis', greetings)

ES6 (со стрелками и шаблонными строками)

'use strict'
const introduce = (firstName, lastName, callback) => {
 let fullName = firstName + ' ' + lastName
 console.log(`Hello ${firstName} ${lastName}`) 
 callback(fullName) 
}
const greetings = (name) => {
 console.log(`It's a pleasure to meet you ${name}`)
}
introduce('Identity', 'Crisis', greetings)

Судя по приведенному выше коду, мы должны увидеть два лога, выведенных на нашу консоль: «Hello Identity Crisis» и «Приятно познакомиться, Identity Crisis». Давайте посмотрим, что возвращает Node.js:

Отлично.

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

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

ES5

function goodbyes () {
  console.log('au revoir')
  function inEnglish () {
    console.log('goodbye')
  }
  return inEnglish
}

ES6 (со стрелочными функциями и шаблонными строками)

'use strict'
const goodbyes = () => {
  console.log('au revoir')
  const inEnglish = () => {
    console.log('goodbye')
  }
  return inEnglish
}

С приведенными выше кодами вам нужно будет вызвать функцию, чтобы вы могли заставить ее работать, либо назначив ее переменной и вызвав переменную, например, var foo = goodbyes() и foo(), либо дважды вызвав прощания, например ()(). Запустите этот код в Node's REPL, чтобы лучше понять его, как в примере ниже.

Обратные вызовы

В нашем предыдущем примере мы рассмотрели функции более высокого порядка с помощью функции introduce. Давайте запустим код и посмотрим, как это относится к обратным вызовам.

'use strict'
const introduce = (firstName, lastName, callback) => {
 let fullName = firstName + ' ' + lastName
 console.log(`Hello ${firstName} ${lastName}`) 
 callback(fullName) 
}
const greetings = (name) => {
 console.log(‘It’s a pleasure to meet you ${name}`)
}
introduce('Identity', 'Crisis', greetings)

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

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

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

'use strict'
const introduce = (firstName, lastName, callback) => {
 let fullName = firstName + ' ' + lastName
 console.log(`Hello ${firstName} ${lastName}`) 
 callback(fullName) 
}
const greetings = (name) => {
 console.log(`It’s a pleasure to meet you ${name}`)
}
const partingWays = (name) => {
  console.log(`It was a pleasure meeting you ${name}, I hope we meet each other soon`)
}
introduce('Identity', 'Crisis', greetings)
introduce('Identity', 'Crisis', partingWays)

Если мы перенесем это на Nodejs, мы должны ожидать, что при первом вызове introduce будет записано «Hello Identity Crisis» и «Приятно познакомиться, Identity Crisis», а при втором вызове introduce для регистрации «Привет, кризис идентичности» и «Было приятно познакомиться с вами, кризис идентичности, надеюсь, мы скоро встретимся»

Посмотрим, что у нас получится:

Великолепный.

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

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

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

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

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

Обратные вызовы так часто используются в JavaScript, что кодовые базы замусорены обратными вызовами, вызывающими обратные вызовы, в результате чего первоначальные обратные вызовы становятся функциями более высокого порядка и так далее. Это в конечном итоге привело к появлению термина в разработке JavaScript под названием Callback Hell.

Надеюсь, этот пост в блоге помог вам.

Опечатки, отзывы, вопросы и комментарии можно направлять в мой твиттер (@dooraven) или оставить комментарий здесь.