我正在尝试学习如何使用生成器和 yield,所以我尝试了以下方法,但它似乎不起作用。
我正在使用以下函数,其中包含 2 个异步调用:
var client = require('mongodb').MongoClient;
$db = function*(collection, obj){
var documents;
yield client.connect('mongodb://localhost/test', function*(err, db){
var c = db.collection(collection);
yield c.find(obj).toArray(function(err, docs){
documents = docs;
db.close();
});
});
return documents.length;
};
然后调用原始调用,我这样做:
var qs = require("querystring");
var query = qs.parse("keywords[]=abc&keywords[]=123");
var total = $db("ads", {"details.keywords": {$in: query["keywords[]"]}});
console.log(total);
当我将输出返回到控制台时,我得到了这个:
{}
我期待的是一个数字,例如 200
。我做错了什么?
最佳答案
长话短说
对于简短的回答,您正在寻找像 co 这样的 helper 。
var co = require("co");
co(myGen( )).then(function (result) { });
但是为什么?
ES6 迭代器或定义它们的生成器没有任何内在的异步性。
function * allIntegers ( ) {
var i = 1;
while (true) {
yield i;
i += 1;
}
}
var ints = allIntegers();
ints.next().value; // 1
ints.next().value; // 2
ints.next().value; // 3
不过,.next( )
方法实际上允许您将数据在 中发送回迭代器。
function * exampleGen ( ) {
var a = yield undefined;
var b = yield a + 1;
return b;
}
var exampleIter = exampleGen();
exampleIter.next().value; // undefined
exampleIter.next(12).value; // 13 (I passed 12 back in, which is assigned to a)
exampleIter.next("Hi").value; // "Hi" is assigned to b, and then returned
这可能会令人困惑,但当你 yield 时,它就像一个 return 语句;左侧尚未分配值... ...更重要的是,如果您将 var y = (yield x) + 1;
括号解析 在表达式的其余部分之前... ...所以你返回,+1 被搁置,直到返回一个值。
然后当它到达时(通过 .next( )
传入),对表达式的其余部分进行求值(然后分配给左侧)。
每次调用返回的对象有两个属性 { value: ..., done: false }
value
是您返回/产生的内容,done
是它是否命中函数末尾的实际 return 语句(包括隐式返回)。
这是可以用来实现这种异步魔法的部分。
function * asyncGen ( id ) {
var key = yield getKeyPromise( id );
var values = yield getValuesPromise( key );
return values;
}
var asyncProcess = asyncGen( 123 );
var getKey = asyncProcess.next( ).value;
getKey.then(function (key) {
return asyncProcess.next( key ).value;
}).then(function (values) {
doStuff(values);
});
没有魔法。
我没有返回一个值,而是返回一个 promise 。
当 promise 完成时,我使用 .next( result )
将结果推回,这让我得到了另一个 promise 。
当该 promise 解决时,我使用 .next( newResult )
等将其推回,直到完成。
我们可以做得更好吗?
我们现在知道我们只是在等待 promises 的解析,然后用结果在迭代器上调用 .next
。
我们是否必须提前知道迭代器的样子,才能知道何时完成?
不是真的。
function coroutine (iterator) {
return new Promise(function (resolve, reject) {
function turnIterator (value) {
var result = iterator.next( value );
if (result.done) {
resolve(result.value);
} else {
result.value.then(turnIterator);
}
}
turnIterator();
};
}
coroutine( myGen ).then(function (result) { });
这并不完整和完美。 co 涵盖额外的基础(确保所有 yield 都像 promise 一样对待,所以你不会通过传递非 promise 值而爆炸......或允许产生 promise 数组,这成为一个 promise ,它将返回该 yield 的结果数组 ...方式,感谢迭代器上的 .throw(err)
方法)。
这些东西并不难实现,但它们使示例比需要的更加困惑。
这正是为什么 co 或其他一些“协程”或“spawn”方法非常适合这种东西的原因。
Express 服务器背后的人构建了 KoaJS,使用 Co 作为库,而 Koa 的中间件系统只是在其 .use
方法中使用生成器并做正确的事情。
但是等等,还有更多!
从 ES7 开始,规范很可能会为这个确切的用例添加语言。
async function doAsyncProcess (id) {
var key = await getKeyPromise(id);
var values = await getValuesPromise(key);
return values;
}
doAsyncProcess(123).then(values => doStuff(values));
async
和 await
关键字一起使用,以实现与协程包装的 promise-yielding 生成器相同的功能,无需所有外部样板(并且使用引擎级优化,最终)。
如果您正在使用像 BabelJS 这样的转译器,您今天就可以试试这个。
希望对您有所帮助。
关于javascript - 使用 yield 等待异步代码完成,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29827443/