异步生成器使用内部队列来处理同步的下一个、抛出和返回方法调用。
我试图构建一种情况,其中此队列对于迭代本身的成功是必需的。因此,我正在寻找一些情况,其中手动实现异步迭代接口,而不自定义重新实现队列,是不够的。
下面是一个示例,但不是很好,因为不保持一般时间一致性,但每一步的迭代结果都是正确的:
function aItsFactory() {
let i = 1;
return {
async next() {
if(i > 5) return Promise.resolve({ value: void 0, done: true });
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${i++}`).then(x => x.json());
return Promise.resolve({ value: res, done: false });
},
[Symbol.asyncIterator]() {
return this;
}
}
}
const ait = aItsFactory();
// general time consistency is lost, because e.g. the fourth call
// is started with the previous three and it could end before the others.
// But the 'i' state is correctly shared so the fifth call
// is correctly requesting the element number five to the source
// and the last call will correctly receive { done: true }
;(async () => {
ait.next();
ait.next();
ait.next();
ait.next();
console.log(await ait.next()); // { done: false, value: { userId: 1, id: 5, title: ... } }
console.log(await ait.next()); // { done: true, value: undefined }
})();
可以说,如果没有适当的队列,迭代概念本身就会丢失。这是因为活动的并行下一个调用。
无论如何,我想找到一些例子,也是微不足道的例子,这些例子清楚地表明异步生成器是创建格式良好的异步迭代对象的更好方法,而不是手动实现异步迭代接口。
------编辑------
让我们谈谈改善的情况:
function aItsFactory() {
let i = 1;
let done = false;
return {
async next() {
if (done) return Promise.resolve({
done: true,
value: undefined
});
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${i++}`).then(x => x.json());
if (Object.keys(res).length === 0) { // the jsonplaceholder source is out of bounds
done = true;
return Promise.resolve({
done: true,
value: undefined
});
} else {
return Promise.resolve({
done: false,
value: res
});
};
},
[Symbol.asyncIterator]() {
return this;
}
}
}
const ait = aItsFactory();
// now lot of sync call to 'ait.next'
这里的done
分辨率是完全异步的。 从异步迭代的角度来看,代码是错误的,因为应强制每个next
调用await
前一次的结果,以了解它是否是最后一次有效的迭代。在这种情况下,当前next
应该什么都不做,立即返回Promise.resolve({done:true, value:undefined})
. 这要归功于同步next
呼叫队列。
但在实践中,越界、反复调用ait.next()
的主要风险是一些无用的 AJAX 请求。 不要误解我,我不是说我们可以视而不见。 关键是异步迭代本身的每一步都不会中断。
我希望看到一种情况,不要太不现实,如果所有下一个调用都没有排队,迭代本身可能会在每一步受到损害。
以下方案:
您有一组数据集进入,例如来自某个 API。您希望对每个数据集进行一些繁重的计算,这就是将数据集发送给另一个工作人员的原因。但有时 API 可能会一次发送多个数据集,并且您不希望同时运行大量工作线程,而是希望拥有有限数量的工作线程。在该数据集中,您正在搜索特定结果。使用异步迭代器,您可以将其编写为:
const incoming = createSomeAsyncIterator();
async function processData() {
let done, value;
while(!done) {
({ done, value } = await incoming.next());
if(!done) {
const result = await searchInWorker(value);
if(result) {
incoming.return();
return result;
}
}
}
}
// Consume tasks in two workers.
Promise.race([
processData(), processData()
]).then(gold => /*...*/);
如果.next()
不按顺序返回数据集,上面的代码将失败。然后,尽管搜索已经完成,其中一名工人可能仍在继续。或者,两个工作人员可能处理同一个数据集。
或者费率石灰示例(从Bergi:)窃取(:
async function* rateLimit(limit, time) {
let count = 0;
while(true) {
if(count++ >= limit) {
await delay(time);
count = 0;
}
yield; // run api call
}
}
const userAPIRate = rateLimit(10, 1000);
async function getUser(id) {
await userAPIRate.next();
return doCall("/user/", id);
}
或者想象一下,你想以某种形式的图库(在 React 中(显示图片流:
const images = streamOfImages();
const Image = () => {
const [image, setImage] = useState(null);
useEffect((async ( ) => {
if(image) await delay(10000); // show image at least 10secs
const { value } = await images.next();
setImage(value);
}, [image]);
return <img src={image || "loading.png"} />;
};
const Gallery = () => <div>
<Image /> <Image /> <Image />
</div>;
另一个是将数据转移到工作线程上,以便一次运行一个进程:
const worker = (async function* () {
let task;
while(true) task = yield task && await doInWorker(task);
})();
worker.next();
worker.next("task 1").then(taskOne => ...);
worker.next("task 2").then(taskTwo => ...);