生成器+异步/等待,异步生成

时间:2016-10-23 06:50:44

标签: javascript ecmascript-6 async-await generator ecmascript-next

我有2个非常相关的问题。一个实际的,与我所遇到的真正问题有关,还有一个理论问题。让我从后者开始:

Q1:async / await + generator有意义吗?

一般来说,在回调或承诺中产生收益并没有多大意义。问题(以及其他问题)是在多个产量之间可能存在竞争,无法判断发电机何时结束等等。

function* this_is_nonsense() {
   setTimeout(() => { yield 'a'; }, 500);
   setTimeout(() => { yield 'b'; }, 500);
   return 'c';
}

当使用async / await时,这些问题似乎实际上已经消失了

function timeout(ns) {
   return new Promise((resolve) => setTimeout(resolve, ns));
}

async function* this_could_maybe_make_sense() {
   // now stuff is sequenced instead of racing
   await timeout(500);
   yield 'a';
   await timeout(500);
   yield 'b';
   return 'c';
}

我认为目前没有这样的事情(如果错误请纠正我!)。所以我的第一个问题是,使用async / await生成器是否存在概念问题?如果没有,是否在任何路线图上?似乎实现需要的不仅仅是将两个功能混合在一起。

Q2:如何编写懒惰的wiki爬虫

这是我所拥有的真实代码问题的人为版本。

想象一下编写一个遍历维基百科的函数,从一些任意页面开始并反复跟随链接(无论如何 - 可以是深度优先,广度优先还是其他东西)。假设我们可以异步抓取维基百科页面以获取链接页面。类似的东西:

async function traverseWikipedia(startLink) {
  const visited = {};
  const result = [];
  function async visit(link) {
     if (visited[link]) { return; }
     visited[link] = true;
     result.push(link);

     const neighboringLinks = await getNeighboringWikipediaLinks(link);

     for (const i = 0; i < neighboringLinks.length; i++) {
        await visit(neighboringLinks[i]);
     }

     // or to parallelize:
     /*
     await Promise.all(
       neighboringLinks.map(
         async (neighbor) => await visit(neighbor)
       )
     );
     */
  );
  await visit(startLink);
  return result;
}

这很好......除了有时我只想要前10个链接,这将会抓取大量的链接。有时我正在搜索包含子串'Francis'的前10个。我想要一个懒惰的版本。

从概念上讲,我需要Promise<Array<T>>或至少Generator<T>,而不是Generator<Promise<T>>的返回类型。我希望消费者能够做任意逻辑,包括异步停止条件。这是一个假的,完全错误的(我假设)版本:

function* async traverseWikipedia(startLink) {
  const visited = {};
  function* async visit(link) {
     if (visited[link]) { return; }
     visited[link] = true;
     yield link;

     const neighboringLinks = await getNeighboringWikipediaLinks(link);
     for (const i = 0; i < neighboringLinks.length; i++) {
        yield* await visit(neighboringLinks[i]);
     }
  );
  yield* await visit(startLink);
}

这与Q1的废话相同,将异步/等待+生成器组合起来就像它做了我想要的那样。但是,如果我 拥有它,我可以像这样消费:

function find10LinksWithFrancis() {
   const results = []
   for (const link of traverseWikipedia()) {
      if (link.indexOf('Francis') > -1) {
          results.push(link);
      }
      if (results.length === 10) {
          break;
      }
   }
   return results;
}

但我也可以搜索不同数量的结果和字符串,具有异步停止条件等。例如,它可以显示10个结果,当用户按下按钮时,继续爬行并显示下一个10。

我没有使用promises / callbacks,没有async / await,只要我仍然为消费者提供相同的API。

生成器的调用者可以执行异步操作。所以我认为有可能实现这一点,其中生成器函数产生邻居的承诺,并且由调用者调用gen.next与生成的实际邻居。类似的东西:

function* traverseWikipedia(startLink) {
  const visited = {};
  function* visit(link) {
     if (visited[link]) { return; }
     visited[link] = true;
     yield { value: link };

     const neighboringLinks = yield { fetch: getNeighboringWikipediaLinks(link) };
     for (const i = 0; i < neighboringLinks.length; i++) {
        yield* visit(neighboringLinks[i]);
     }
  );
  yield* visit(startLink);
}

但是使用起来并不是很好......你必须检测实际结果的情况而不是生成器要求你给它回答答案,它的类型安全性较低等等。我只想要看起来像{ {1}}以及使用它的相对简单的方法。这可能吗?

编辑添加:我想到你可以实现这个,如果生成器函数的主体可以访问它以某种方式返回的生成器...不知道如何做到这一点

再次编辑以添加消费者的示例,并注意:如果消费者也是生成器,我也没关系,如果这有帮助的话。我认为下面有某种引擎层可能是可能的,让人想起redux-saga。不确定是否有人写过这样的东西

0 个答案:

没有答案