首先也是最重要的,重要的是在您的头脑中建立一个好的模型来准确地了解生成器是什么。生成器函数是一个返回生成器对象的函数,该生成器对象将yield
在您调用它时逐步执行生成器函数中的语句.next()
。
鉴于该描述,您应该注意到未提及异步行为。对生成器本身的任何操作都是同步的。您可以立即运行到第一个yield
,然后执行 asetTimeout
然后调用.next()
转到下一个yield
,但它是setTimeout
导致异步行为的原因,而不是生成器本身。
因此,让我们根据fs.readdir
. fs.readdir
是一个异步函数,单独在生成器中使用它不会有任何效果。让我们看看你的例子:
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.
希望它可以更清楚地说明您仍在readdir
同步调用的内容,并且您没有传递任何回调,因此它可能会抛出错误或其他东西。
那么如何从生成器中获得良好的行为呢?
通常,这是通过让生成器产生一个特殊对象来完成的,该对象表示readdir
实际计算值之前的结果。
对于(不切实际的)示例,yield
函数是产生表示值的东西的简单方法。
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.
});
实际上,您会希望这种类型的逻辑继续调用生成器,直到所有yield
调用都完成,而不是对每个调用进行硬编码。不过要注意的主要一点是,现在生成器本身看起来是同步的,并且read
函数之外的所有内容都是超级通用的。
您需要某种生成器包装函数来处理此产值过程,而您的示例suspend
正是这样做的。另一个例子是co
。
“返回代表值的东西”方法的标准方法是返回 apromise
或 athunk
因为像我一样返回一个函数有点难看。
使用thunk
andco
库,您可以在没有示例函数的情况下执行上述操作:
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.
});
更新
您的更新仍然有些混乱。如果您希望您的list
函数成为生成器,那么您将需要在调用它的任何地方co
之外使用。list
里面的一切都co
应该是基于生成器的,而外面的一切都co
应该是基于回调的。co
不会list
自动异步。co
用于将基于生成器的异步流控制转换为基于回调的流控制。
例如
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);
})
}