admin管理员组文章数量:1328601
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:
,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 e 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 }
?
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./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 e 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 }
?
- related: JS build object recursively (answer uses tj/co) – Mulan Commented Aug 26, 2016 at 18:00
3 Answers
Reset to default 4The 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;
}
}
In the if
you only have a return
on one side.
You also don't need to use the .then
inside your generator. The whole point of using the generator is so that you don't have to touch the promise API within.
Instead, call recurse
with count + 1
function* recurse(limit = 5, count = 0) {
if(count < limit) {
yield sleep(1000).then(() => Promise.resolve(++count));
return yield* recurse(limit, count + 1);
}
else {
return count;
}
}
And since you're using ES6, while we're at it …
return function co() {
let generator = generatorFn.apply(this, arguments);
… is better off as …
return function co(...args) {
let generator = generatorFn(...args)
all together now
Run the snippet and you'll see the correct output right here
let log = console.log.bind(console);
let err = console.error.bind(console);
function coroutine(generatorFn){
return function co(...args) {
let generator = generatorFn(...args)
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)
return yield* recurse(limit, count + 1);
}
else {
return count;
}
}
let test = coroutine(recurse);
test().then(log).catch(err);
For those who wonder: this is not how the coroutine
helper is meant to be used. The function itself should recurse through the wrapperd version, like this:
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);
});
}
const recurse = coroutine(function* (
limit = 5, count = 0
) {
if(count < limit) {
yield sleep(100);
++count;
return yield recurse(limit, count);
} else {
return count;
}
});
recurse().then(log).catch(err);
Why?
An asynchronous function is defined as regular function, returning Promise
, and never throwing a synchronous exception. The coroutine
helper just helps you write asynchronous functions. If you are familiar with async/await from other languages, this wrapper is intended to convert generators to asynchronous functions, where all await
s are replaced by yield
s. These functions are easier to reason about with this in mind, so people don't have to argue about generators, just asynchronous functions.
本文标签: recursionHow do I return from a recursive generator function in JavaScriptStack Overflow
版权声明:本文标题:recursion - How do I return from a recursive generator function in JavaScript? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742257895a2441966.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论