First and foremost, it is important to have a good model in your head of exactly what a generator is. A generator function is a function that returns a generator object, and that generator object will step through yield
statements within the generator function as you call .next()
on it.
Given that description, you should notice that asynchronous behavior is not mentioned. Any action on a generator on its own is synchronous. You can run to the first yield
immediately and then do a setTimeout
and then call .next()
to go to the next yield
, but it is the setTimeout
that causes asynchronous behavior, not the generator itself.
So let's cast this in the light of fs.readdir
. fs.readdir
is an async function, and using it in a generator on its own will have no effect. Let's look at your example:
function * read(path){
return yield fs.readdir(path);
}
var gen = read(path);
// gen is now a generator object.
var first = gen.next();
// This is equivalent to first = fs.readdir(path);
// Which means first === undefined since fs.readdir returns nothing.
var final = gen.next();
// This is equivalent to final = undefined;
// Because you are returning the result of 'yield', and that is the value passed
// into .next(), and you are not passing anything to it.
Hopefully it makes it clearer that what you are still calling readdir
synchronously, and you are not passing any callback, so it will probably throw an error or something.
So how do you get nice behavior from generators?
Generally this is accomplished by having the generator yield a special object that represents the result of readdir
before the value has actually been calculated.
For (unrealistic) example, yield
ing a function is a simple way to yield something that represents the value.
function * read(path){
return yield function(callback){
fs.readdir(path, callback);
};
}
var gen = read(path);
// gen is now a generator object.
var first = gen.next();
// This is equivalent to first = function(callback){ ... };
// Trigger the callback to calculate the value here.
first(function(err, dir){
var dirData = gen.next(dir);
// This will just return 'dir' since we are directly returning the yielded value.
// Do whatever.
});
Really, you would want this type of logic to continue calling the generator until all of the yield
calls are done, rather than hard-coding each call. The main thing to notice with this though, is now the generator itself looks synchronous, and everything outside the read
function is super generic.
You need some kind of generator wrapper function that handles this yield value process, and your example of the suspend
does exactly this. Another example is co
.
The standard method for the method of "return something representing the value" is to return a promise
or a thunk
since returning a function like I did is kind of ugly.
With the thunk
and co
libraries, you with do the above without the example function:
var thunkify = require('thunkify');
var co = require('co');
var fs = require('fs');
var readdir = thunkify(fs.readdir);
co(function * (){
// `readdir` will call the node function, and return a thunk representing the
// directory, which is then `yield`ed to `co`, which will wait for the data
// to be ready, and then it will start the generator again, passing the value
// as the result of the `yield`.
var dirData = yield readdir(path, callback);
// Do whatever.
})(function(err, result){
// This callback is called once the synchronous-looking generator has returned.
// or thrown an exception.
});
Update
Your update still has some confusion. If you want your list
function to be a generator, then you will need to use co
outside of list
wherever you are calling it. Everything inside of co
should be generator-based and everything outside co
should be callback-based. co
does not make list
automatically asynchronous. co
is used to translate a generator-based async flow control into callback-based flow control.
e.g.
list: function(path, callback){
co(function * (){
var list = yield readdir(path);
// Use `list` right here.
return list;
})(function(err, result){
// err here would be set if your 'readdir' call had an error
// result is the return value from 'co', so it would be 'list'.
callback(err, result);
})
}