【问题标题】:Making an asynchronous generator *[Symbol.asyncIterator] without async/await but with promises only制作一个没有 async/await 但只有 Promise 的异步生成器 *[Symbol.asyncIterator]
【发布时间】:2021-06-07 15:55:57
【问题描述】:

现在这段代码可以正常工作了

async *[Symbol.asyncIterator](){
  var promise;
  while (true){
    promise = this.#HEAD.promise;
    this.size--;
    this.#HEAD.next ? this.#HEAD = this.#HEAD.next
                    : this.#LAST = void 0;
    yield await promise;
  };
};

如果我不想使用 async / await 抽象,那么我如何仅使用 Promise 实现相同的功能?

我天真地尝试过

*[Symbol.asyncIterator](){
  var promise;
  while (true){
    promise = this.#HEAD.promise;
    this.size--;
    this.#HEAD.next ? this.#HEAD = this.#HEAD.next
                    : this.#LAST = void 0;
    promise.then(yield);
  };
};

但它返回undefined;假设yield 不是函数。我检查了this 的问题,但这与生成器无关,也没有涉及yield。有没有办法实现这个?

编辑: yield await promise 在异步生成器中似乎很浪费。请改用yield promise。检查 T.J. 下的 cmets。人群回答。

【问题讨论】:

  • 旁注:在异步迭代器代码中修改this.size 看起来非常可疑。迭代器永远不应该改变它们正在迭代的状态。

标签: javascript asynchronous async-await iterator generator


【解决方案1】:

异步迭代器必须为结果对象({value, done} 形式的对象)生成 Promise。你不能在非async 生成器中通过使用promise 作为yield 的操作数来做到这一点,因为你给yield 的操作数在结果对象中变成了value,不是结果对象本身。也就是说,当你这样做时:

yield 42;

...在async 生成器函数中,它为{value: 42, done: false} 生成一个promise(使用TypeScript 表示法,Promise<{value: 42, done: false}>)。如果你这样做:

yield somePromise;

...在一个-async生成器函数中,它产生{value: somePromise, done: false}(TS:{value: Promise, done: false})。这不是 asyncIterator 函数定义返回的内容。它必须为{value, done} 对象返回一个promise,而不是一个non-promise 对象。

所以如果你想避免使用async/await,你至少有两个选择:

  1. 定义您的对象,使其不是异步可迭代的,只是可迭代的,并且它产生的值是{value: Promise, done: boolean}

  2. 将其定义为异步可迭代并且不要使用yield。显式编写next 方法。

对于语义,我肯定会选择 #2。如果没有关于您的对象的更多信息,很难准确显示,但大致如下:

[Symbol.asyncIterator](){
    let current = this.#HEAD;
    return {
        next() {
            if (/*done condition*/) {
                return Promise.resolve({done: true});
            }
            return current.promise.then(value => {
                current = current.next; // or whatever
                return {value, done: false};
            });
        }
    };
}

或者,如果您希望异步迭代器对象具有标准原型,请将其放在可以重用的地方:

const asyncIteratorPrototype =
    Object.getPrototypeOf(
        Object.getPrototypeOf(
            (async function *(){}).prototype
        )
    );

然后:

[Symbol.asyncIterator](){
    let current = this.#HEAD;
    return Object.assign(Object.create(asyncIterator), {
        next() {
            if (/*done condition*/) {
                return Promise.resolve({done: true});
            }
            return current.promise.then(value => {
                current = current.next; // or whatever
                return {value, done: false};
            });
        }
    });
}

【讨论】:

  • FWIW,我在最近一本书 JavaScript: The New Toys 的第 9 章中详细介绍了 async 迭代器和生成器,包括在不使用异步生成器的情况下实现异步迭代器函数语法(虽然我仍然使用async/await——但根据第 9 章和上一章关于 Promise 的内容,将其转换为非async 语法相当简单)。如果您有兴趣,请在我的个人资料中链接。
  • 谢谢。我可以通过在AsyncQueue() 构造函数的原型中显式提供.next() 功能来实现非常相似的功能(这个sn-p 是AsyncQueue btw 的一部分),即使没有进入像*[Symbol.asyncIterator]() 这样的生成器但使用[Symbol.asyncIterator](){return this;}。我只是想知道yield 是否仍然适用于*[Symbol.asyncIterator]()?一个同步的while 在普通发电机中停止就可以了,所以我希望在这里也一样。
  • @Redu - (抱歉,我忘记删除 *。)好点,如果使用生成器的代码是 async 函数,则产量所在的循环可能是异步(即使对于非async 迭代器),因为生成器是一个状态机,所以使用它的代码的async-ness 扩展到正在使用的迭代器。 :-) “我只是想知道yield 是否仍然适用于*[Symbol.asyncIterator]()?” 我不这么认为。我已经更新了答案以解释原因。
  • @Redu - 如果你认为 JavaScript 正试图变得实用,恐怕你会失望。 :-) 虽然这些年来已经添加了一些功能性的东西,但这似乎不是任何一种总体方向,至少在过去几年中不是。无论如何,编码愉快!
  • @Redu - 由于您接受async 而不是await 的妥协是您的工作而不是我的工作,因此我建议将其发布为答案- 尽管它确实回答了与原始问题不同的问题张贴。有时候世界是不完美的。 :-) 希望以上内容有用。 (对我来说。)
猜你喜欢
  • 2018-02-15
  • 2018-06-11
  • 2018-08-22
  • 1970-01-01
  • 2019-12-04
  • 1970-01-01
  • 2019-11-14
  • 2023-03-10
  • 1970-01-01
相关资源
最近更新 更多