Рубрики
Без рубрики

Асинхронное программирование на JavaScript и обратные вызовы

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

  • Синхронность в языках программирования
  • язык JavaScript
  • Обратные вызовы
  • Обработка ошибок при обратных вызовах
  • Проблема с обратными вызовами
  • Альтернативы обратным вызовам

Синхронность в языках программирования

Компьютеры асинхронны по своей конструкции.

Асинхронность означает, что все может происходить независимо от основного потока программы.

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

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

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

Обычно языки программирования являются синхронными, и некоторые из них предоставляют способ управления асинхронностью в языке или с помощью библиотек. C, Java, C#, PHP, Go, Ruby, Swift, Python – все они синхронны по умолчанию. Некоторые из них обрабатывают асинхронность с помощью потоков, порождая новый процесс.

язык JavaScript

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

Строки кода выполняются последовательно, одна за другой, например:

const a = 1
const b = 2
const c = a * b
console.log(c)
doSomething()

Но JavaScript родился внутри браузера, его основной задачей вначале было реагировать на действия пользователя, такие как onClick , onMouseOver При наведении курсора , При обмене , при отправке и так далее. Как это можно сделать с помощью модели синхронного программирования?

Ответ был в его окружении. Браузер предоставляет способ сделать это, предоставляя набор API, которые могут обрабатывать такого рода функции.

Совсем недавно, Node.js введена неблокирующая среда ввода-вывода, чтобы распространить эту концепцию на доступ к файлам, сетевые вызовы и так далее.

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

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

document.getElementById('button').addEventListener('click', () => {
  //item clicked
})

Это так называемый обратный вызов .

Обратный вызов – это простая функция, которая передается в качестве значения другой функции и будет выполняться только тогда, когда произойдет событие. Мы можем это сделать, потому что JavaScript имеет первоклассные функции, которые могут быть назначены переменным и переданы другим функциям (называемым функциями более высокого порядка )

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

window.addEventListener('load', () => {
  //window loaded
  //do what you want
})

Обратные вызовы используются везде, а не только в событиях DOM.

Одним из распространенных примеров является использование таймеров:

setTimeout(() => {
  // runs after 2 seconds
}, 2000)

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

const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
  if (xhr.readyState === 4) {
    xhr.status === 200 ? console.log(xhr.responseText) : console.error('error')
  }
}
xhr.open('GET', 'https://yoursite.com')
xhr.send()

Обработка ошибок при обратных вызовах

Как вы справляетесь с ошибками при обратных вызовах? Одна очень распространенная стратегия состоит в том, чтобы использовать то, что Node.js принято: первым параметром в любой функции обратного вызова является объект ошибки: ошибка-первые обратные вызовы

Если ошибки нет, объект равен null . Если есть ошибка, она содержит некоторое описание ошибки и другую информацию.

fs.readFile('/file.json', (err, data) => {
  if (err !== null) {
    //handle error
    console.log(err)
    return
  }

  //no errors, process data
  console.log(data)
})

Проблема с обратными вызовами

Обратные вызовы отлично подходят для простых случаев!

Однако каждый обратный вызов добавляет уровень вложенности, и когда у вас много обратных вызовов, код начинает очень быстро усложняться:

window.addEventListener('load', () => {
  document.getElementById('button').addEventListener('click', () => {
    setTimeout(() => {
      items.forEach(item => {
        //your code here
      })
    }, 2000)
  })
})

Это всего лишь простой 4-уровневый код, но я видел гораздо больше уровней вложенности, и это не весело.

Как нам решить эту проблему?

Альтернативы обратным вызовам

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

  • Обещания (ES2015)
  • Асинхронный/Ожидание (ES2017)

Оригинал: “https://flaviocopes.com/javascript-callbacks/”