Почему при использовании redux-saga оператор yield является значением выполнения, а не неопределенным?

Допустим, у нас есть следующая функция генератора:

function* testGenerator() {

  const result = yield Promise.resolve('foobar').then(res => res);

  console.log(result);

}

Если я запускаю этот генератор со следующей строкой, он регистрирует undefined

const test = testGenerator();
test.next();
test.next();

Но в саге такие строки будут логироваться foobar. Мне просто любопытно, какой механизм стоит за этим (присвоение результата yield переменной)

Изменить:

TL;DR:

ссылка на функцию MDN*

Вызов метода next() с аргументом возобновит выполнение функции генератора, заменив оператор yield, в котором выполнение было приостановлено, аргументом из next().

По сути, чтобы заставить его регистрировать «foobar», просто передайте значение первого yield второму следующему, чтобы при его возобновлении оператор yield заменялся значением:

function* testGenerator() {
  const result = yield Promise.resolve('foobar');
  console.log(result);
}

const test = testGenerator();
test.next().value.then(r => test.next(r))

person sunquan    schedule 28.02.2017    source источник
comment
yield возвращает все, что передано next(). Вы ничего не проходите.   -  person a better oliver    schedule 28.02.2017


Ответы (1)


Просмотр кода

Прежде всего, избавьтесь от этого; это ничего не делает

const result = yield Promise.resolve('foobar').then(res => res);

Ваш генератор работает нормально

Если я запускаю этот генератор со следующей строкой, он регистрирует undefined

Нет, это не так. Он будет регистрировать {next, value} каждый раз

function* testGenerator() {
  const result = yield Promise.resolve('foobar');
  console.log(result);
}

let test = testGenerator()

console.log(test.next())
// { value: Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 1}, done: false }

console.log(test.next())
// { value: undefined, done: true }

Регистрация промисов сложна. Если вы мне не верите, посмотрите это

function* testGenerator() {
  const result = yield Promise.resolve('foobar');
  console.log(result);
}

let test = testGenerator()

test.next().value.then(console.log)
// "foobar"


Сопрограммы

Но в саге такие строки будут логироваться foobar. Мне просто любопытно, какой механизм стоит за этим (присвоение результата yield переменной)

Redux Saga построена так, чтобы проходить через генератор и обрабатывать различные эффекты. Обещание — это лишь одна из многих вещей, которые вы можете дать.

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

  • yield отправляет данные из генератора
  • gen.next(x) отправляет следующее значение в генератор

Таким образом, мы используем эту возможность генераторов для отправки промиса out и отправки значения resolved промиса обратно in, что позволяет разрешенному промису значение, которое будет присвоено непосредственно переменной

const coro = gen => {
  const next = x => {
    const {value, done} = gen.next(x)
    if (done)
      return value
    else
      return value.then(next)
  }
  return next()
}

function* testGenerator () {
  const x = yield Promise.resolve(1)
  console.log(x) // 1

  const y = yield Promise.resolve(2)
  console.log(y) // 2

  const z = yield Promise.resolve(3)
  console.log(z) // 3

  return x + y + z
}

coro(testGenerator()).then(console.log)
// "6"


async и await

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

Изменения:

  • нам больше не нужен помощник coro
  • используйте ключевое слово async вместо генератора function*
  • используйте ключевое слово await вместо yield

Поведение такое же.

  • используйте await, чтобы отправить обещание
  • Разрешенное значение обещания отправляется обратно в качестве возвращаемого значения вызова await
  • Функции async неявно возвращают Promise, поэтому цепочка вызовов .then позволяет получить окончательное значение.

const testRoutine = async () => {
  const x = await Promise.resolve(1)
  console.log(x) // 1

  const y = await Promise.resolve(2)
  console.log(y) // 2

  const z = await Promise.resolve(3)
  console.log(z) // 3

  return x + y + z
}

testRoutine().then(console.log)
// "6"


Доморощенные сопрограммы

Вы не должны запускать свои собственные сопрограммы, потому что есть много вещей, на которые нужно обратить внимание. Например, если генератор выдает throws ошибку или yields отклоненное обещание, приведенная выше реализация просто проглотит его.

В демонстрационных целях я покажу вам, как это сделать, но если вы хотите охватить все свои основы, вам следует прочитать исходный код Redux Saga или исходный код код другой библиотеки сопрограмм, такой как tj/co

const coro = gen => {
  return new Promise((resolve, reject) => {
    const next = x => {
      try {
        let {value, done} = gen.next(x)
        return done ? resolve(value) : value.then(next, reject)
      }
      catch (err) {
        reject(err)
      }
    }
    next()
  })
}

function* noYield() { return 5 }

function* throwsUp() { throw Error("OOPS") }

function* yieldReject() { yield Promise.reject('NO') }

function* normal() { let x = yield Promise.resolve(16); return x * x; }

coro(noYield())
  .then(console.log, console.error) // 5

coro(throwsUp())
  .then(console.log, console.error) // [Error: OOPS]
  
coro(yieldReject())
  .then(console.log, console.error) // "NO"
  
coro(normal())
  .then(console.log, console.error) // 256

person Mulan    schedule 28.02.2017
comment
записывается undefined... Нет, не записывается. Заходит. Запустите код. - person a better oliver; 28.02.2017
comment
Большое спасибо. Кроме {next, value} в первом фрагменте вы также записали undefined на самом деле, и это то, что я имел в виду в вопросе (почему результат yield отличается, как он заменяется). gen.next(x) отправляет следующее значение в генератор. Это демистифицирует это, спасибо. Я должен был более внимательно прочитать основы функции генератора. И спасибо за тщательную проработку вокруг него. - person sunquan; 28.02.2017