Две асинхронные функции — add и mul

const add = (x, y) => Promise.resolve(x + y)
const mul = (x, y) => Promise.resolve(x * y)

Чтобы проиллюстрировать различные асинхронные конструкции, мы напишем другую функцию, которая делает что-то эквивалентное:

const fn = (x, y, z) => console.log(mul(add(x, y), z))

Использование промисов

Код для этого довольно прост.

const foo = (x, y, z) => {
  add(x, y)
    .then((addResult) => mul(addResult, z))
    .then((mulResult) => console.log(`${mulResult}`))
}

Использование генераторов

Использование генераторов позволяет нам писать код, который выглядит линейно. Только нам придется оборачивать генератор в сопрограмму.

const bar = coroutine(function* (x, y, z) {
  let addResult = yield add(x, y)
  let mulResult = yield mul(addResult, z)
  console.log(`${mulResult}`)
})

Приведенная ниже функция сопрограммы принимает обещание, полученное от генератора, и передает его обратно генератору с помощью метода next его итератора. Так происходит до тех пор, пока в генераторе не останется операторов yield.

const coroutine = generator => function() {
  let it = generator.apply(null, arguments)
  let next = it.next()
  let coInterval = setInterval(() => {
    next.done ?
      clearInterval(coInterval) :
      next.value.then(result => next = it.next(result))
  })
}

Использование асинхронного/ожидания

Структура кода аналогична синхронно написанной функции (так же, как генератор/выход), но без необходимости оборачивать нашу функцию в сопрограмму (как в случае с генераторами).

const baz = async (x, y, z) => {
  let addResult = await add(x, y)
  let mulResult = await mul(addResult, z)
  console.log(`${mulResult}`);
}