8

我在babel的帮助下尝试使用 ES6 生成器,但我无法理解如何(或者如果!)我可以有效地使用基于回调的异步函数来输出迭代器。

假设我希望能够编写一个接收多个 url 的函数,异步下载它们并在下载后立即返回它们。我希望能够编写如下内容:

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'' 是在回调内部调用的,而不是直接在生成器内部调用的。我可以在网上找到许多尝试相同的人的示例,它们要么不太透明),要么需要启用浏览器/节点标志,要么使用特定于节点的功能/库。最接近我需要的库似乎是task.js,但我什至无法在当前 Chrome 上运行最简单的示例。

有没有办法使用标准和当前功能来获得预期的行为,(当前我的意思是可以与 babel 之类的编译器一起使用,但不需要在浏览器上启用额外的标志)还是我必须等待async/await

4

3 回答 3

2

2019 年更新

通过回调产生实际上非常简单。因为你只能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上实时尝试。

于 2019-06-14T07:05:06.477 回答
0

有没有办法使用标准和当前功能获得预期的行为

是的,使用承诺和生成器。许多 Promise 库和一些独立的库都使用生成器“协程”。

但请注意,您不能将迭代与异步混合使用,您只能使用生成器。您的示例似乎使他们有些困惑-您似乎希望该for ( {url, data} of downloadUrls(urls) ) {循环同步工作,但这是行不通的。

我必须等待async/await吗?

不,您不必等待,Babel 已经支持它们

于 2015-04-17T12:58:43.903 回答
0

这是一种使用生成器/迭代器来扁平化异步代码的干净方法,它在 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)返回失败。

我刚刚想出了这个模式,所以可能有一些我还没有找到的陷阱,但它似乎可以工作,并且允许非常扁平的代码添加最少的内容。

于 2016-06-03T13:54:09.927 回答