Javascript 使用yield等待异步代码完成
我试图学习如何使用发电机和产量,所以我尝试了下面的方法,但似乎不起作用 我正在使用以下函数,其中包含2个异步调用:Javascript 使用yield等待异步代码完成,javascript,node.js,yield,Javascript,Node.js,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.c
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
这样的数字。我做错了什么?Yield和生成器与异步无关,它们的主要目的是生成可编辑的值序列,如下所示:
function * gen() {
var i = 0;
while (i < 10) {
yield i++;
}
}
for (var i of gen()) {
console.log(i);
}
函数*gen(){
var i=0;
而(i<10){
产量i++;
}
}
for(gen()的变量i){
控制台日志(i);
}
仅仅用星号(生成器函数)调用函数只会创建生成器对象(这就是为什么您在控制台中看到{}
),可以使用next
函数与之交互
也就是说,您可以使用生成器函数作为异步函数的模拟,但您需要一个特殊的运行程序,如。TL;博士
简而言之,您正在寻找像co这样的助手
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
这可能会让人困惑,但当你屈服时,它就像一个回报声明;左侧尚未指定值。。。更重要的是,如果你把vary=(收益率x)+1代码>在表达式的其余部分之前解析括号。。。因此您返回,+1被保留,直到返回一个值。
然后,当它到达时(通过.next()
传入),表达式的其余部分将被计算(然后分配到左侧)
每次调用返回的对象有两个属性{value:…,done:false}
value
是您返回/产生的内容,done
是它是否在函数末尾命中实际返回语句(包括隐式返回)
这是可以用来实现异步魔法的部分
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);
});
没有魔法。
我返回的不是一个值,而是一个承诺。
当承诺完成时,我将使用.next(result)
将结果推回,这将为我带来另一个承诺
当这个承诺解决后,我会用.next(newResult)
等把它推回去,直到我完成为止
我们能做得更好吗?
我们现在知道,我们只是在等待承诺得到解决,然后在迭代器上调用.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包括额外的基数(确保所有收益都像承诺一样处理,这样你就不会因为传递一个非承诺值而崩溃……或者允许产生承诺数组,这将成为一个承诺,它将返回该收益的结果数组……或者尝试/捕获承诺处理,将错误扔回迭代器……是的,try/catch有效通过迭代器上的.throw(err)
方法,可以很好地处理yield语句
这些东西并不难实现,但它们使示例比需要的更为模糊
这就是为什么co或其他一些“协同程序”或“生成”方法非常适合这种东西的原因
Express服务器背后的人使用Co作为库构建了KoaJS,而Koa的中间件系统只在其中使用生成器。使用方法并做正确的事情
但是等等,还有更多!
从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
关键字一起使用,以实现与协同程序生成器相同的功能,而无需所有外部样板文件(并最终进行引擎级优化)
如果您使用的是像BabelJS这样的transpiler,您今天就可以试试这个
我希望这有帮助
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);
实际上,total
是$db
生成器函数的迭代器。您可以通过total.next().value
检索其yield
值。但是,mongodb库是基于回调的,因此其函数不返回值,因此yield
将返回null
您提到您在其他地方使用了Promission;我建议您看看,尤其是它的功能。Promisification反转回调模型,以便回调的参数现在用于解析promisified函数。更好的是,它将转换整个基于回调的API
最后,bluebird还提供了协同路由功能;但是它的协同路由必须返回承诺。因此,您的代码可能被重写如下:
var mongo = require('mongodb');
var Promise = require('bluebird');
//here we convert the mongodb callback based API to a promised based API
Promise.promisifyAll(mongo);
$db = Promise.coroutine(function*(collection, obj){
//existing functions are converted to promised based versions which have
//the same name with 'Async' appended to them
return yield mongo.MongoClient.connectAsync('mongodb://localhost/test')
.then(function(db){
return db.collectionAsync(collection);})
.then(function(collection) {
return collection.countAsync();});
});
var qs = require("querystring");
var query = qs.parse("keywords[]=abc&keywords[]=123");
$db('ads',{"details.keywords": {$in: query["keywords[]"]}})
.then(console.log)
您可能想研究如何使异步调用有组织且简单…但这并不能帮助我了解它是如何工作的…我在另一种方法中使用了Promise
,这与此相同吗?不。生成承诺是您需要在迭代器内部执行的操作,从生成器的实例化返回。co
接受这些产生的承诺,并将其值返回到下一轮迭代器中。这里的第一个看起来像是使用mongo API的一个非常简洁的示例。您如何重写它,使DB连接不在函数中?即如何等待一件事,然后再做下一件事?