ccnokes ccnokes - 3 months ago 31
Javascript Question

How do I return from a recursive generator function in JavaScript?

I'm playing around with a recursive generator function that returns values asynchronously. I'm using a coroutine wrapper function to call it. Code and JSBin below:

http://jsbin.com/nuyovay/edit?js,console

let log = console.log.bind(console);
let err = console.error.bind(console);

function coroutine(generatorFn){
return function co() {
let generator = generatorFn.apply(this, arguments);

function handle(result) {
console.log(result);
if (result.done) {
return Promise.resolve(result.value);
}
return Promise.resolve(result.value)
.then(
res => handle(generator.next(res)),
err => handle(generator.throw(err))
);
}

try {
return handle(generator.next());
} catch (err) {
return Promise.reject(err);
}
};
}

function sleep(dur) {
return new Promise(res => {
setTimeout(() => { res() }, dur);
});
}

function* recurse(limit = 5, count = 0) {
if(count < limit) {
yield sleep(100).then(() => Promise.resolve(++count));
yield* recurse(limit, count);
}
else {
return count;
}
}

let test = coroutine(recurse);

test().then(log).catch(err);


Running this returns:

Object {value: Promise, done: false}
Object {value: Promise, done: false}
Object {value: Promise, done: false}
Object {value: Promise, done: false}
Object {value: Promise, done: false}
// `value` should be 5
Object {value: undefined, done: true}


How come the final
return
from the generator is
undefined
? When I adapt the above for use with bluebird's
Promise.coroutine
, I get the same result. Am I missing something fundamental about recursive generators? How do I get it to
{ value: 5, done: true }
?

Answer

The issue is that you are returning count, but you're returning it in the parent generator. Unlike yield in delegated generators, return is not yielded back up through the delegation chain automatically.

If you want to get the return value of a delegated generator, you have to assign it directly in the parent generator:

let returnValue = yield* recurse(limit, count);

Since you're using "recursive" generators (multiple levels of delegation), you would need to repeat the process and return the value at every level of delegation:

function* recurse(limit = 5, count = 0) {   
    if(count < limit) {
        yield sleep(100).then(() => Promise.resolve(++count));
        let result = yield* recurse(limit, count); // save the return value
        return result; // return it to the parent
    }
    else {
        return count;
    }
}
Comments