Давайте начнем с начала асинхронных операций в JS, чтобы понять, что асинхронность происходит постоянно. Итак, добро пожаловать в прекрасный мир операций с задержкой по времени.

Программа JavaScript почти всегда разбита на части, первая часть выполняется сейчас, а следующая - позже, в ответ на событие. Несмотря на то, что программа выполняется по частям, все они разделяют масштаб и состояние программы. Каждый раз, когда есть события для запуска, цикл обработки событий выполняется до тех пор, пока очередь не станет пустой. В очереди содержатся фрагменты кода для обработчиков взаимодействия с пользователем, событий таймера, операций ввода / вывода и т. Д.

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

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

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

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

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

Но что, если бы мы могли отменить эту инверсию управления? Что, если вместо того, чтобы передать продолжение нашей программы другой стороне, мы могли бы ожидать, что она вернет нам возможность узнать, когда ее задача завершится, и тогда наш код мог бы решить, что делать дальше?

Обещания

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

 Promise         +----------+
+-------+        |fulfilled |
|pending|   =>   +----------+
+-------+        | rejected |
                 +----------+

Основной синтаксис создания обещания

var promise = new Promise(function(resolve, reject) {
  // This function will be executed automatically
  // It's body may contain any async code. After successful
  // completion resolve should be called, after error - reject.
}).then(
  onFulfilled, // callback triggered if resolve action
  onRejected // callback triggered if reject action
);

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

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

Но это еще не конец. Обещания стали основой для очень интересного асинхронного шаблона, такого как async / await.

Асинхронные функции

Ходит много слухов об использовании async / await вместо обещаний. Стоит ли вообще использовать это в процессе разработки?

Все функции, входящие в спецификацию ES2015, можно просмотреть здесь. Эта спецификация получила название ES6. Иногда люди думают, что все, что не включено в ES6, является частью функций ES7. Это неправильное понимание окружающего. Информацию о том, что входит в ES2016, вы можете найти здесь. Итак, как правило, включены только две основные функции: оператор возведения в степень ** и Array.prototype.includes. И в этом списке полностью не упоминается async / await.

Итак, что вообще такое async / await? Вообще говоря - это Обещание.

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

export function getPayload = async (url) => {
  const {payload, success} = await fetch(url);
  if (success) {
    dispatch({type: 'SUCCESS'});
  } else {
    dispatch({type: 'FAILURE'});
  }
}

Как это работает

Основным моментом здесь является определение функции с флагом async, который работает не только для общих функций, но и для стрелочных функций, классов, статических функций. Упомянутый в теле флага await сигнализирует, что основной код должен подождать, пока не произойдет какое-либо событие. Это должен быть маркер Promise перед асинхронным маркером, который должен быть разрешен или отклонен. Только после этого основной код продолжит выполнение. Await делает то же самое, что и функция .then для Promise. Но главный плюс такого подхода в том, что нам не нужны обратные вызовы для обработки возвращаемого значения. Это создает ощущение использования синхронного кода.

Такой синхронный вид создает естественное ощущение, что вы дома и можете использовать try / catch везде (и это действительно так). И это единственный способ отловить ошибку .

Почему лучше?

  1. Лаконично и ясно. Нам не нужно писать .then, создавать анонимную функцию для обработки ответа или давать имя data переменной, которую нам не нужно использовать. Мы также избегаем вложенности нашего кода.
  2. Обработка ошибок. Async / await наконец-то позволяет обрабатывать как синхронные, так и асинхронные ошибки с помощью одной и той же конструкции, старого доброго try/catch.
  3. Условные выражения. Гораздо проще создать условную логику, которая реализует ее внутри обратных вызовов. Со всем гнездовьем ада.
  4. Промежуточные значения. Это удобно, чтобы можно было легко манипулировать средними асинхронными данными, просто поместив их в точку останова. И вы можете быть уверены, что на этом код остановится (а не как обратные вызовы стрелочных функций для обещаний).
  5. Стек ошибок. Стек ошибок, возвращаемый из цепочки обещаний, не дает представления о том, где произошла ошибка. Хуже того, это заблуждение. Однако стек ошибок из async / await указывает на функцию, содержащую ошибку.
  6. Отладка. Иногда это хороший шанс обнаружить ошибку, просто используя такие кнопки инструментов разработчика, как step-into, step-out, step-over и т. д. Вы можете полностью использовать их с подходом async / away. Но они бесполезны, когда вы используете обещания.

А если?…

Ожидание отсутствует

async function get(a) {
  return a;
}
get('hi'); // 'hi'
// behaves completely in sync way, just returns a

Последовательность

async function getResult() {
  const names = await getNames(); // wait till resolve
  const statuses = await getStatusesFor(names); // wait till resolve
  return statuses;
}
getResult();
// first waits for resolving getNames continue to run
// then resolving getStatusesFor, after resolving returns result
// such code will be executed step by step

Параллельный

async getAggregatedData() {
  const [names, profiles] = await Promise.all([getNames(), getProfiles()]);
  return {names, profiles};
}

Современные браузеры

В современных браузерах выполнено около 93% -99% реализации ES2015 (Safari - лидер на данный момент). Когда вы начинаете новый проект, это хороший знак. Но, к сожалению, для реализации async / await вам всегда нужно использовать внешние инструменты, такие как Babel, для компиляции такого кода. А иногда это может добавить лишнюю сложность. Большинство асинхронных проблем могут быть эффективно решены с помощью генераторов. Но это другая история.

NodeJS

Официально он представлен в версии 7.6. Итак, пора играть прямо из коробки :)