Co — это библиотека JavaScript для потока управления на основе генератора. Асинхронная функция — это функция ES2017 для обработки асинхронного потока управления в JavaScript. И функция co, и функция async достигают одной и той же цели: обрабатывать асинхронный поток управления в синхронном синтаксисе без использования обратных вызовов или связывания промисов. У них также очень похожий синтаксис (пример).
Co — популярная библиотека, тем более, что она используется веб-фреймворком Koa в версии 1.x. Однако, поскольку асинхронная функция становится общедоступной с браузерами и Node 7.6+, переход на нее может принести следующие преимущества:
- Простая ментальная модель. Интересно узнать о генераторе, функциях генератора и о том, как работает кооперация. Но асинхронная функция требует относительно гораздо более простой ментальной модели, которая, как мы надеемся, может привести к более высокой производительности и меньшему количеству ошибок.
- Улучшенная трассировка стека и удобство отладки. И функция co, и функция async работают со стеком асинхронных вызовов в инструментальных средствах, но использование библиотеки co приводит к более неясной трассировке стека из-за дополнительных стеков-оболочек. Нативная асинхронная функция дает оптимальные стеки, соответствующие только вашему коду.
- Одной зависимостью меньше. Несмотря на то, что библиотека co невелика (~250 LOC и 1,2 КБ минимизирована) и не имеет зависимостей (что само по себе полезно), удаление зависимостей и лишней оболочки из кода всегда Ницца.
- Подготовьтесь к будущему с помощью библиотек и инструментов. Поддержка асинхронных функций улучшается, и можно предвидеть, что со временем co будет заменена в большинстве случаев использования. Например, koa@2 уже переключился на асинхронную функцию.
Миграция
Вам не нужно мигрировать все сразу, но вы можете предпринять шаги и мигрировать по одной функции за раз.
Предположим, что у вас есть функция-генератор
function * foo(bar)
который переносится на асинхронную функцию
async function foo(bar)
Перенос абонентов
Есть несколько возможных типов вызывающих абонентов foo
.
Звонящий сбрасывает вызов
Это подразумевает, что вызывающая функция сама является функцией-генератором. Первый сценарий прост:
// before
yield foo(someBar)
// after
yield foo(someBar) // no migration needed
Так как новый foo
возвращает Promise
, который выдается co, вам не нужно ничего делать здесь.
С другой стороны, будьте осторожны со следующим случаем:
// before yield foo
// afteryield foo()
Это может быть немного странно, потому что функции-генераторы также могут возвращаться с помощью co, и поэтому предыдущая версия эквивалентна yield foo()
. Но новая foo
больше не является функцией-генератором, и в результате co будет рассматривать ее как санк. Однако наш новый foo
не является преобразователем и никогда не разрешится так, как это сделал бы преобразователь, что означает, что yield foo
никогда не вернется, что приведет к потенциально неприятной ошибке. Решение состоит в том, чтобы выдать результат функции — обещание вместо этого.
Звонящий co
завершает вызов
// before co(foo(someBar))
// afterfoo(someBar)
// before co(foo) // after foo()
В предыдущей версии co()
запускает старую функцию генератора/генератора и возвращает обещание. Поскольку новый foo
возвращает обещание напрямую, co()
больше не требуется.
// before co.wrap(foo)
// after foo
co.wrap
оборачивает старую foo
(функцию-генератор) в функцию, не являющуюся генератором, которая возвращает Promise
. По той же причине, что и выше, поскольку новый foo()
возвращает Promise
напрямую, не требуется никакой совместной упаковки.
Звонящий использует yield *
// before
yield * foo
// after: same as yield
Вы, вероятно, не должны иметь это, так как библиотеке co не нужен yield *
и в большинстве случаев yield
было бы достаточно. Поэтому обработка его как yield
, вероятно, вам нужна.
Перенос вызываемых абонентов
foo
может вызывать другие функции (т. е. вызываемые) с использованием yield
, которые необходимо перенести, поскольку foo
больше не является функцией-генератором и, следовательно, не может уступать.
Callee основан на обещаниях
// before yield somePromise
// after await somePromise
// before yield somePromiseReturningFunction
// afterawait somePromiseReturningFunction()
// before yield somePromiseReturningFunction(args)
// after await somePromiseReturningFunction(args)
Это прямые миграции, и все готово. Поздравляю!
Вызываемый объект является генератором/генератором-функцией
// before yield someGeneratorFunction
// after await co(someGeneratorFunction)
// before yield someGeneratorFunction(args)
// afterawait co(someGeneratorFunction(args))
Поскольку вызываемые функции являются функциями генератора/генератора (т. е. еще не перенесенными), мы можем восполнить пробел, используя co, чтобы превратить их в функции, возвращающие обещания, которые затем могут быть await
-ed.
Полезность совместного выхода
// before yield [a, b]
// after, assuming a and b are promises Promise.all([a, b])
// beforeyield { a, b }
// after, assuming a and b are promisesBluebird.props({ a, b })
Это приятные мелочи вокруг yield
, которые напрямую не связаны с await
. Поэтому мы должны обращаться с ними более явно.
Примечание. Если какой-либо из a, b
не является Promise
, его необходимо перенести специально. например, Promise.all([a, co(b)])
.