Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ECMA6 generators: yield promise

As I understand it ECMA6 generators are supposed to be able to yield to a function that returns a promise, eventually returning the resolved/rejected. Making the code read more like synchronous code, and avoiding callback hell.

I am using node.js v0.12.2 with --harmony and the following code.

var someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    resolve("I'm Resolved!");
  });
};

someAsyncThing().then(function(res) {console.log(res);});
// Works as expected: logs I'm Resolved!

function* getPromise() {
    var x = yield someAsyncThing();
    console.log("x: " + x); // Fails x undefined
}

var y = getPromise();
console.log(y); // returns {}

console.log(y.next());
// Fails: logs { value: {}, done: false }

I've based the code off of the few examples I have been able to find online. What am I doing wrong?

like image 487
Ryhnn Avatar asked May 22 '15 16:05

Ryhnn


2 Answers

As I understand it ECMA6 generators are supposed to be able to yield to a function that returns a promise, eventually returning the resolved/rejected.

No, that's not their purpose. ES6 generators are supposed to provide a simple way for writing iterators - each call to a generator function produces an iterator. An iterator is just a sequence of values - like an array, but consumed dynamically and produced lazily.

Now, generators can be abused for asynchronous control flow, by producing a sequence of promises that is consumed asynchronously and advancing the iterator with the results of each awaited promise. See here for an explanation without promises.

So what your code is missing is the consumer that actually waits for the promises and advances your generator. Usually you'd use a dedicated library (like co or task.js), or one of the helper functions that many promise libraries provide (Q, Bluebird, when, …), but for the purposes of this answer I'll show a simplified one:

function run(gf) {
    let g = gf();
    return Promise.resolve(function step(v) {
         var res = g.next(v);
         if (res.done) return res.value;
         return res.value.then(step);
    }());
}

Now with this function you can actually "execute" your getPromise generator:

run(getPromise).then(console.log, console.error);
like image 151
Bergi Avatar answered Oct 13 '22 04:10

Bergi


tl;dr: The promise yielded by the generator has to move the generator forward.


If you look at first examples in http://davidwalsh.name/async-generators, you will notice that the async function actually moves the iterator forward:

function request(url) {
    // this is where we're hiding the asynchronicity,
    // away from the main code of our generator
    // `it.next(..)` is the generator's iterator-resume
    // call
    makeAjaxCall( url, function(response){
        it.next( response );               // <--- this is where the magic happens
    } );
    // Note: nothing returned here!
}

But since you are working with promises, we can improve on that, a little bit. y.next().value returns a promise, and you'd have to listen to that promise. So instead of writing console.log(y.next()), you'd write:

var promise = y.next().value;
promise.then(y.next.bind(y)); // or promise.then(function(v) { y.next(v); });

Babel demo

Of course this not very practical, because now you cannot access the next yielded value and you don't know when the generator will be done. However, you could write a recursive function which takes care of that. That's what runGenerator introduced later in this article takes care of.

like image 27
Felix Kling Avatar answered Oct 13 '22 05:10

Felix Kling