10

异步生成器使用内部队列来处理同步的nextthrowreturn方法调用。

我试图构建一种情况,该队列对于迭代本身的成功是必需的。因此,我正在寻找一些情况,即手动实现异步迭代接口而无需自定义重新实现队列是不够的。

下面是一个例子,但不是很好,因为没有保持一般的时间一致性,但迭代结果在每一步都是正确的:

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 请求。不要误会,我不是说我们可以视而不见。关键是异步迭代本身的每一步都不会被破坏。

我希望看到一种情况,不是太不切实际,如果所有下一个调用都没有排队,迭代本身可能会在每一步受到损害。

4

1 回答 1

3

以下场景:

你有一个数据集流进来,例如来自一些 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 => ...);
于 2019-08-20T20:49:41.363 回答