ES6 生成器:将回调转换为迭代器
Posted
技术标签:
【中文标题】ES6 生成器:将回调转换为迭代器【英文标题】:ES6 generators: transforming callbacks to iterators 【发布时间】:2015-06-24 07:11:04 【问题描述】:我正在babel 的帮助下尝试使用 ES6 生成器,但我无法理解如何(或者如果!)我可以有效地使用基于回调的异步函数来输出迭代器。
假设我希望能够编写一个函数,该函数接受多个 url,异步下载它们并在下载后立即返回它们。 我希望能够编写如下内容:
let urls = ['http://www.google.com', 'http://www.***.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 之类的编译器一起使用,但不需要在浏览器上启用额外的标志)还是我必须等待 @987654329 @?
【问题讨论】:
这些在线解释对您来说究竟是如何不“透明”的?大卫沃尔什的文章是我读过的最好的文章之一(但你当然需要阅读整个系列) 似乎相关:异步生成器提案github.com/jhusain/asyncgenerator(AFAIK 尚不支持)。 【参考方案1】:有没有办法使用标准和当前功能获得预期的行为
是的,使用承诺和生成器。许多 Promise 库,以及一些独立的库,都使用生成器“协程”。
但请注意you cannot mix iteration with asynchrony,您只能使用生成器。您的示例似乎让他们有些困惑 - 看起来您希望 for ( url, data of downloadUrls(urls) )
循环同步工作,但这是行不通的。
我必须等待
async/await
吗?
不,您不必等待,Babel already supports them!
【讨论】:
【参考方案2】:这是一种使用生成器/迭代器来扁平化异步代码的简洁方法,它在 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(result)返回成功并恢复执行,或者通过调用iterator.throw(error )。
我只是想出了这个模式,所以可能有一些我还没有找到的陷阱,但它似乎工作并且允许非常扁平的代码,只需要最少的添加。
【讨论】:
【参考方案3】:2019 年更新
通过 回调产生实际上非常简单。由于您只能直接从生成器 function*
出现的位置(而不是从回调)调用 yield
,因此您需要生成一个 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 上实时尝试。
【讨论】:
以上是关于ES6 生成器:将回调转换为迭代器的主要内容,如果未能解决你的问题,请参考以下文章
ES6中的迭代器(Iterator)和生成器(Generator)