我在babel的帮助下尝试使用ES6生成器,我无法理解如何(或者是否!)我可以有效地使用基于回调的异步函数来输出迭代器。
让我们说我希望能够编写一个带有大量网址的函数,异步下载它们并在下载后立即返回它们。 我希望能够写出如下内容:
let urls = ['http://www.google.com', 'http://www.stackoverflow.com' ];
for ( {url, data} of downloadUrls(urls) ) {
console.log("Content of url", url, "is");
console.log(data);
}
如何实施downloadUrls
?
理想情况下,我希望能够写下以下内容:
var downloadUrls = function*(urls) {
for( let url of urls ) {
$.ajax(url).done( function(data) {
yield data;
});
}
};
这当然不起作用,因为``yield''正在回调中调用,而不是直接在生成器内部调用。 我可以在网上找到许多尝试相同的例子,它们是not much transparent),需要enabling browser/node flags,或者使用特定于节点的功能/库。 最接近我需要的库似乎是task.js,但我甚至无法在最新的Chrome上运行最简单的示例。
有没有办法使用标准和当前功能获得预期的行为,(目前我的意思是可以使用像babel这样的转换器,但不需要在浏览器上启用额外的标志)或者我必须等待{{ 1}}?
答案 0 :(得分:1)
是否有办法使用标准和当前功能获得预期的行为
是的,使用承诺和生成器。许多承诺库和一些独立的库都使用了生成器" coroutines"。
但请注意you cannot mix iteration with asynchrony,您只能使用生成器。你的例子似乎让他们感到困惑 - 看起来你期望for ( {url, data} of downloadUrls(urls) ) {
循环同步工作,这是行不通的。
我必须等待
async/await
吗?
不,你不必等,Babel already supports them!
答案 1 :(得分:1)
这是一种使用生成器/迭代器来展平异步代码的简洁方法,该代码在node.js中适用于我:
var asyncProcedureGenerator1 = function*() {
var it = yield(0); //get a reference to the iterator
try {
var a = yield (asyncPart1.bind(it))(0); //call the function, set this = it
var b = yield (asyncPart2.bind(it))(a);
var c = yield (asyncPart3.bind(it))(b);
console.log("c = ", c);
}
catch(err)
{
console.log("Something went wrong: ", err);
}
};
var runAsyncGenerator = function(generator) {
var asyncProcedureIterator = generator(); //create an iterator
asyncProcedureIterator.next(); //start the iterator
asyncProcedureIterator.next(asyncProcedureIterator); //pass a reference of the iterator to itself
}
var asyncPart1 = function(param1) {
var it = this; //the iterator will be equal to this.
console.log("Starting asyncPart1 with param1 = ", param1);
setTimeout(function() {
console.log("Done with asyncPart1");
var returnValue = 42 + param1;
console.log("asyncPart1 returned ", returnValue);
it.next(returnValue); //when we are done, resume the iterator which has yielded to us.
},2000);
};
var asyncPart2 = function(param1) {
var it = this; //the iterator will be equal to this.
console.log("Starting asyncPart2 with param1 = ", param1);
setTimeout(function() {
console.log("Done with asyncPart2");
var returnValue = param1 / 2;
console.log("asyncPart2 returned ", returnValue);
//it.throw("Uh oh.");
it.next(returnValue);
},2000);
};
var asyncPart3 = function(param1) {
var it = this; //the iterator will be equal to this.
console.log("Starting asyncPart3 with param1 = ", param1);
setTimeout(function() {
console.log("Done with asyncPart3");
var returnValue = param1 / 3;
console.log("asyncPart3 returned ", returnValue);
it.next(returnValue);
},2000);
};
runAsyncGenerator(asyncProcedureGenerator1);
这个想法是运行生成器,创建一个迭代器,然后将该迭代器的引用传递给它自己。
然后迭代器可以调用异步函数(带有yield)并向它们传递一个对它自己的引用,它允许这些函数返回成功并通过调用iterator.next(结果)或通过调用iterator.throw失败来恢复执行(错误)。
我刚刚提出了这种模式,所以可能还有一些我尚未找到的问题,但它似乎可以工作并且允许非常平坦的代码而且添加量极少。
答案 2 :(得分:1)
通过回调产生实际上非常简单。由于您只能直接从出现的生成器yield
调用function*
(而不是从回调函数),因此您需要产生一个Promise,它将从回调函数resolve
d中进行调用:
async function* fetchUrls(urls) {
for (const url of urls)
yield new Promise((resolve, reject) => {
fetch(url, { mode: 'no-cors' }).then(response => resolve(response.status));
});
}
(async function main() {
const urls = ['https://www.ietf.org/rfc/rfc2616.txt', 'https://www.w3.org/TR/PNG/iso_8859-1.txt'];
// for-await-of syntax
for await (const status of fetchUrls(urls))
console.log(status);
}());
如果该示例在浏览器中不起作用(由于Cross Origin Read Blocking,我返回0而不是200),请尝试在repl.it上使用它。