Why can yield not resolve promise?

Viewed 2193

I have the following code:

object = {
    getPastEvents: () => Promise.resolve([1,2,3])
}

function* fetchPastEvents() {
    const values = yield object.getPastEvents()
    console.log(values)
}

const events = fetchPastEvents()
events.next()
events.next()

Now I'd like to know how I can create a function getPastEvents that returns a defined values. Right now, values is undefined. I thought that yield can resolve a promise like async await.

3 Answers

When you call .next, you resume running the generator function. If you want to resume it with a specific value, you pass a value into .next. So if you want to replicate redux-saga's behavior of resolving promises and then resuming generator, you'll need to take the promise yielded by the first call to .next, wait for the promise to resolve by using its .then method, and then call .next with the value it resolved to.

const object = {
  getPastEvents: () => Promise.resolve([1,2,3])
}

function* fetchPastEvents() {
  console.log("entry")
  const values = yield object.getPastEvents()
  console.log(values)
}

const iterator = fetchPastEvents()
const promise = iterator.next().value;
promise.then(val => iterator.next(val))

I'd recommend you don't do this yourself though. If this is a saga as you said, then run it via redux saga and you'll get this behavior for free. Or as others have mentioned, the co library implements similar functionality.

I thought that yield can resolve a promise like async await.

No, yield is for yielding a generator function which means that it halts the functions execution at that point, returns the expression on right side to the caller of .next() and then takes the value passed into the next .next(value) call to continue the execution. That is somehow asynchronous, but has nothing todo with async / await. You could however yield a promise, and call the next .next(value) when the promise resolves:

 async function co(generator) {
   let iterator = generator();
   let done, value;
   do {
     ({ done, value } = iterator.next(await value);
   } while(!done);
   return value;
}

Usable as:

 co(function* doAsync() {
   let result = yield somePromise();
   console.log(result);
});

but I don't know how that could be useful, especially since there are now async generators exactly for that usecase.

Using yield in generator doesn't guarantee to wait until promise is full-filled.

To make your example works, you have to write the code to invoke next method of object that implements iterable protocol.

function runGenerator(asyncGen) {
    const gen = asyncGen();
    let returnValue;

    (function iterate(value){
        returnValue = gen.next(value);

        if(!returnValue.done) {
            returnValue.value.then(iterate);
        }
    })();
}

const object = {
    getPastEvents: () => Promise.resolve([1,2,3])
};

runGenerator(function*() {
    const values = yield object.getPastEvents();
    console.log(values);
});

Note that this is just simple implementation, you have to check more conditions if you want to use actual projects.

Rather than just implementing your own for this, I would recommend to use co module.


Note that Async & Await is very similar to this approach, they both requires promisified API as well.

However to use Async & Await, make sure your JavaScript Engine supports, else you have to transpile to make them work in older engines.

But using generator will works most of modern JavaScript engine, it's quite bit old specification(ES6).

Also generally transpiling Async & Await makes lots of code which could be the problem if you want to save the content size as small as possible.

Key difference of ES6 Generator and ES7 Async & Await is that ES6 Generator can't use "Arrow function" which really critical some situations(you have to save the "this" reference somewhere before get into generator function's context), however ES7 Async Function can do that.

Note that ES7 Async & Await is just syntactic sugar of promise API, but ES6 generator doesn't.

Related