我有2个非常相关的问题。一个实际的,与我所遇到的真正问题有关,还有一个理论问题。让我从后者开始:
一般来说,在回调或承诺中产生收益并没有多大意义。问题(以及其他问题)是在多个产量之间可能存在竞争,无法判断发电机何时结束等等。
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生成器是否存在概念问题?如果没有,是否在任何路线图上?似乎实现需要的不仅仅是将两个功能混合在一起。
这是我所拥有的真实代码问题的人为版本。
想象一下编写一个遍历维基百科的函数,从一些任意页面开始并反复跟随链接(无论如何 - 可以是深度优先,广度优先还是其他东西)。假设我们可以异步抓取维基百科页面以获取链接页面。类似的东西:
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。不确定是否有人写过这样的东西