TL;DR
简而言之,您正在寻找像 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( ) 方法实际上允许您将数据发送回 in 到迭代器。
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
考虑起来可能会令人困惑,但是当您让其产生时,它就像一个返回语句;左侧尚未分配值... ...更重要的是,如果您将 var y = (yield 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 ) 等将其推回,直到完成。
我们可以做得更好吗?
我们现在知道,我们只是在等待 Promise 解决,然后在迭代器上调用 .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 的结果数组......或围绕承诺处理的 try/catch,将错误扔回迭代器......是的,try/catch 与 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 这样的转译器,你今天可以试试这个。
我希望这会有所帮助。