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 
// after
yield foo()

Это может быть немного странно, потому что функции-генераторы также могут возвращаться с помощью co, и поэтому предыдущая версия эквивалентна yield foo() . Но новая foo больше не является функцией-генератором, и в результате co будет рассматривать ее как санк. Однако наш новый foo не является преобразователем и никогда не разрешится так, как это сделал бы преобразователь, что означает, что yield foo никогда не вернется, что приведет к потенциально неприятной ошибке. Решение состоит в том, чтобы выдать результат функции — обещание вместо этого.

Звонящий co завершает вызов

// before
co(foo(someBar)) 
// after
foo(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
// after 
await somePromiseReturningFunction()
// before
yield somePromiseReturningFunction(args)
// after
await somePromiseReturningFunction(args)

Это прямые миграции, и все готово. Поздравляю!

Вызываемый объект является генератором/генератором-функцией

// before
yield someGeneratorFunction
// after
await co(someGeneratorFunction)
// before
yield someGeneratorFunction(args)
// after
await co(someGeneratorFunction(args))

Поскольку вызываемые функции являются функциями генератора/генератора (т. е. еще не перенесенными), мы можем восполнить пробел, используя co, чтобы превратить их в функции, возвращающие обещания, которые затем могут быть await-ed.

Полезность совместного выхода

// before
yield [a, b]
// after, assuming a and b are promises
Promise.all([a, b])
// before
yield { a, b }
// after, assuming a and b are promises
Bluebird.props({ a, b })

Это приятные мелочи вокруг yield, которые напрямую не связаны с await. Поэтому мы должны обращаться с ними более явно.

Примечание. Если какой-либо из a, b не является Promise, его необходимо перенести специально. например, Promise.all([a, co(b)]).

Дальнейшая миграция