Эволюция асинхронного JavaScript
18К открытий18К показов
Функции async
уже практически здесь — но дорога к ним была довольно долгой. Не так давно мы писали коллбэки, потом появились Promise / A+ спецификации, следом за ними — функции-генераторы, и теперь async функции.
Давайте взглянем назад и посмотрим, как асинхронный JavaScript развивался с годами.
Коллбэки
Все началось с коллбэков.
Асинхронный JavaScript
Асинхронное программирование, каким мы его знаем в JavaScript, может быть реализовано только функциями, являющимися первоочередными членами языка: они могут быть переданы как любая другая переменная другим функциям. Так родились коллбэки: если вы передаете функцию другой функции (функции высшего порядка) как параметр, в пределах функции вы можете вызвать её, когда закончите со своей задачей. Никаких возвращаемых значений, вы просто вызываете другую функцию с параметрами.
Эти так называемые error-first коллбэки лежат в сердце Node.js — модули ядра используют их так же, как и большинство модулей, которые вы можете найти в NPM.
Проблемы с коллбэками:
- легко написать “callback hell” или спагетти-код, если не использовать их должным образом;
- легко упустить обработку ошибок;
- нельзя возвращать значения с выражением
return
, как и нельзя использовать ключевое словоthrow
.
В основном из-за этого JavaScript-мир стал искать решения, которые сделали бы асинхронную JavaScript-разработку легче.
Одним из ответов был модуль async. Если вы много работали с коллбэками, то знаете, как сложно может быть заставить что-то, работающее параллельно, работать последовательно; даже маппинг массивов использует асинхронные функции. Тогда благодаря Каолану Макмейхону (Caolan McMahon) родился модуль async.
С async вы можете просто делать так:
Promises
Текущая спецификация JS Promises (в переводе с английского — обещания) берет начало в 2012 и доступна с ES6 — однако обещания не были разработаны сообществом JavaScript. Термин придумал Дэниел Фридман в 1976 году.
Promise представляет собой конечный результат асинхронной операции.
Предыдущий пример с использование обещаний выглядел бы так:
Вы можете заметить, что обещания также используют коллбэки. Как then
, так и catch
регистрируют коллбэки, которые будут вызваны как при успешном завершении асинхронной операции, так и если она по каким-то причинам не будет выполнена. Другой плюс Promises в том, что они могут быть связаны цепочкой:
При использовании обещаний вы можете использовать полифиллы в средах выполнения, в которых их еще нет. Популярный выбор в таких случаях — bluebird. Эти библиотеки могут предоставлять гораздо большую функциональность, чем нативные обещания — но даже в этих случаях следуйте спецификациям Promises/A+.
Вы можете спросить: как я могу использовать обещания, когда большинство библиотек предоставляют только коллбэк-интерфейсы?
Хорошо, это довольно-таки легко: единственная вещь, которую вы должны сделать — это обернуть коллбэк в функцию, возвращающую обещание:
Некоторые библиотеки/фреймворки уже поддерживают интерфейсы и коллбэков, и обещаний. Если вы создаете библиотеку, хорошей практикой будет поддержка обоих. Вы легко можете сделать что-то вроде:
Или даже проще, вы можете можете начать с интерфейсов обещаний и предоставить совместимость с такими инструментами, как callbackify. Callbackify в основном делает те же вещи, что и предыдущий фрагмент кода, но более общим путем.
Генераторы / yield
Генераторы JavaScript — это относительно новый концепт, который был представлен в ES6.
Разве не было бы здорово, если бы при выполнении функции вы могли бы приостанавливать её в какой-либо точке, рассчитывать что-то, делать другие вещи, а затем возвращаться к ней, возможно, с каким-то значением, и продолжать её выполнение?
Это именно то, что функции-генераторы делают для вас. Когда мы вызываем функцию-генератор, она не начинает выполняться, мы будем должны итерировать её вручную.
Если вы хотите легко использовать генераторы для написания асинхронного JavaScript, вам также понадобится пакет co.
С co
наши предыдущие примеры могли бы выглядеть так:
Вы можете спросить: а что с операциями, выполняющимися параллельно? Ответ проще, чем вы могли бы подумать (под этой оберткой всего лишь Promise.all
):
Async / await
Async-функции были представлены в ES7, и в настоящее время доступны только с транспайлерами, такими как babel (сейчас мы говорим о ключевом слове async
, а не о пакете async).
В двух словах, с ключевым словом async
мы можем делать то, что мы делали с комбинацией co и генераторов — кроме хаков.
Под капотом у async
функции обещания — поэтому async-функция возвращает тип Promise
.
Поэтому если мы хотим сделать то же самое, что и предыдущих примерах, мы должны переписать наш код следующим образом:
Как можете видеть, чтобы использовать async-функцию, вы должны поставить ключевое слово async
перед объявлением функции. После этого вы можете увидеть ключевое слово await
внутри созданной async функции.
Параллелизм async-функций довольно-таки схож с yield-подходом – за исключением того, что Promise.all
не скрыта, но вы должны вызвать её:
Koa уже поддерживает async-функции, так что вы можете опробовать их сегодня, используя babel
.
За перевод благодарим нашего подписчика, Александра Хисматулина
18К открытий18К показов