1

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 T.J. Crowders answer.

1
  • Side note: Modifying this.size within the async iterator code looks very suspect. Iterators should never change the state of what they're iterating. Commented Mar 9, 2021 at 18:11

1 Answer 1

4

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};
            });
        }
    });
}
Sign up to request clarification or add additional context in comments.

9 Comments

FWIW, I go into async iterators and generators in some detail in Chapter 9 of my recent book JavaScript: The New Toys, including implementing async iterators without using async generator function syntax (though I did stil use async/await -- but it's fairly straightforward to turn that into non-async syntax based on Chapter 9 and the previous chapter on promises). Links in my profile if you're interested.
Thank you. I can implement something very similar by explicitly providing the .next() functionality in the prototype of the AsyncQueue() constructor (this snippet is a part of AsyncQueue btw) even without getting into generators like *[Symbol.asyncIterator]() but with [Symbol.asyncIterator](){return this;}. I just would like to know if yield is still applicable within *[Symbol.asyncIterator]()? A synchronous while is just fine with halting in normal generators so i expected the same here.
@Redu - (Sorry, I'd forgotten to remove the *.) Good point, if the code using the generator is an async function, the loop the yield is in could be asynchronous (even for a non-async iterator), since the generator is a state machine so the async-ness of the code using it extends to the iterator being used. :-) "I just would like to know if yield is still applicable within *[Symbol.asyncIterator]()?" I don't think so. I've updated the answer to explain why.
@Redu - If you think JavaScript is trying to become functional, I'm afraid you're in for disappointment. :-) While some functional things have been added over the years, that doesn't seem to be any kind of overarching direction, at least not in the last several years. Anyway, happy coding!
@Redu - Since your compromise accepting async but not await is more your work than mine, I suggest posting it as an answer -- although it does answer a different question to the original one posted. Sometimes the world is imperfect. :-) Hopefully the above was useful. (It was to me.)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.