Обратные вызовы против обещаний против асинхронного / ожидания

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

Примером этого может быть вызов службы HTTP, которой может потребоваться 500 миллисекунд для возврата значения при хорошем сетевом соединении или 5 секунд, если сетевое соединение плохое. Вместо того, чтобы блокировать вашу программу на срок до 5 секунд, JavaScript просто позволяет вам сказать, что делать, когда значение действительно вернется, продолжает свой веселый путь и обрабатывает ответ только тогда, когда это необходимо.

На момент написания этой статьи и текущей спецификации JavaScript для ECMAScript8 существует 3 основных способа написания этих асинхронных абстракций. Вот краткий обзор каждого из них.

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

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

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

Пример в JavaScript:

console.log("Start of my code")
const myCallbackFunction = function(dataReturned) {
    console.log("Got the data!")
}
// Simulating a service call that takes 50ms to return data
// Then calls the callback function
setTimeout(myCallbackFunction, 50);
console.log("End of my code")

Должен привести к следующему выводу:

Start of my code
End of my code
Here's the data returned

Запустите его здесь!

Что хорошего: довольно простая абстракция для начинающих, которую легко освоить и отладить.

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

Обещания

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

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

Вы можете передать объект обещания, а когда будете готовы, вы можете использовать встроенную функцию .then для обработки результата или функцию .catch для обработки ошибки.

Пример в JavaScript:

console.log("Start of my code");
function getMyData() {
  return new Promise(function(resolve, reject) {
    // Simulate getting the data successfully
    resolve("Some data");
  });
}
getMyData()
  .then(myData => {
    console.log("Got the data!");
  })
  .catch(error => {
    console.log("something went wrong");
  });
console.log("End of my code");

Должен привести к следующему выводу:

"Start of my code"
"End of my code"
"Got the data!"

Запустите его здесь!

Что хорошего: дает вам больше гибкости для организации кода. Вы можете легко объединить несколько асинхронных операций с .then функциями. Хорошая поддержка библиотеки для обработки параллельных групп асинхронных операций.

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

Асинхронный / Ожидание

Абстракция async / await - новинка для разработчиков, стремящихся быть на переднем крае разработки JavaScript. Он только недавно получил стандартную поддержку в браузерах, но его можно было использовать через транспиляторы, такие как Babel. В основном это связано с тем, что async / await на самом деле просто синтаксический сахар по сравнению с существующей абстракцией Promise.

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

В этом не должно быть ничего нового для любого программиста на языке, который написан с ожиданием синхронного выполнения, например, Java или C ++.

Пример в JavaScript:

function getMyData() {
  return new Promise(function(resolve, reject) {
    // Simulate getting the data successfully
    resolve("Some data");
  })
    .then(function(data){return data});
}
async function getMyAsyncData(){
  console.log("Start of my code");
  
  const myData = await getMyData();
  console.log("Got my Data! ", myData);
  
  console.log("End of my code");
}
getMyAsyncData();

Должен привести к следующему выводу:

"Start of my code"
"Got my Data! " "Some data"
"End of my code"

Запустите его здесь!

Что хорошего: давайте напишем асинхронный код линейно без новых конструкций, таких как функции обратного вызова или синтаксис объекта Promise. Однако, поскольку под капотом всего лишь Promises, его легко отлаживать, и это, безусловно, самый чистый способ сохранить лаконичность кода.

Что не так хорошо: на момент написания этой статьи поддержка async / await все еще набирала обороты как в инструментах, так и среди разработчиков. Он поддерживается всеми основными браузерами и Node, однако для обратной совместимости по-прежнему требуются полифилы. Проверить его текущую поддержку можно здесь.

Последние мысли

Если вы работаете с JavaScript, в какой-то момент вы столкнетесь с одной из этих абстракций. Скорее всего, вам придется самому написать асинхронный код. Хорошее эмпирическое правило: если вы пишете новый код, начните с использования обещаний, так как они лучше всего подходят для будущего сопровождения. По мере роста кодовой базы ваш код должен оставаться более чистым, чем если бы вы использовали обратные вызовы. Если вы видите места, где синтаксис Promise может быть слишком многословным, попробуйте заменить его на async / await. Не забывайте, для каких браузеров / платформ вы создаете, и при необходимости добавляйте транспилеры или полифилы.

Дальнейшее чтение