简体   繁体   中英

Making an asynchronous generator *[Symbol.asyncIterator] without async/await but with promises only

Now this code works just fine

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;
  };
};

Say if i don't want to use the async / await abstraction then how can i implement the same functionality only with promises?

I naively tried

*[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);
  };
};

but it returns undefined ; presumingly yield not being a function. I checked out this question but it's not about generators and no yield is involved. Is there a way to implement this?

Edit: yield await promise in an async generator seems to be wasteful. Use yield promise instead. Check the comments under TJ Crowders answer.

An async iterator has to produce promises for result objects (objects in the form {value, done} ). You can't do that in a non- async generator by using a promise as yield 's operand because the operand you give yield becomes the value in the result object, not the result object itself. That is, when you do:

yield 42;

...in an async generator function, it produces a promise for {value: 42, done: false} (using TypeScript notation, Promise<{value: 42, done: false}> ). If you do:

yield somePromise;

...in a non - async generator function, it produces {value: somePromise, done: false} (TS: {value: Promise, done: false} ). That's not what an asyncIterator function is defined to return. It has to return a promise for the {value, done} object, not a non-promise object.

So you have at least two choices if you want to avoid using async / await :

  1. Define your object such that it isn't async iterable, just iterable, and the values it produces are {value: Promise, done: boolean} .

  2. Define it as async iterable and don't use yield . Write the next method explicitly.

I'd definitely go for #2 for the semantics. It's hard to show precisely without more information about your object, but roughly:

[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};
            });
        }
    };
}

Or if you want the async iterator object to have the standard prototype, put this somewhere where you can reuse it:

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

then:

[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};
            });
        }
    });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM