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.

Redu
  • 25,060
  • 6
  • 56
  • 76
  • Side note: Modifying `this.size` within the async iterator code looks **very** suspect. Iterators should never change the state of what they're iterating. – T.J. Crowder Mar 09 '21 at 18:11

1 Answers1

2

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};
            });
        }
    });
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 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. – T.J. Crowder Mar 09 '21 at 18:12
  • 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 Mar 09 '21 at 18:51
  • @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. – T.J. Crowder Mar 10 '21 at 09:15
  • As now i understand it all boils down to, `async *[Symbol.asyncIterator]() ` and `*[Symbol.asyncIterator]()` changingthe what `yield` does fundementally. In this case yes... we **must** use `async *[Symbol.asyncIterator]() ` as we want `yield` to work different than the `yield` in a synchronous generator. Now `yield` waits for the promise to resolve and packs up the result in a `Promise<{value: stg, done:false}>` and... does it all by itself. There seems to be no need for `yield await promise` but just `yield promise` is sufficient. i haven't see this mentioned anywhere. --> – Redu Mar 10 '21 at 17:01
  • So in the first snippet of my question i rephrase `yield await promise;` into `yield promise;` and it works just fine. So i call it a compromise since i have to use `async` at the head of the generator function but can avoid `await`s. If you agree with me please mention this in your answer and i will accept it. – Redu Mar 10 '21 at 17:06
  • @Redu - So you just want to avoid `await`? Your question says you want to avoid `async`/`await`. :-) BTW, I deleted my comment above. Interestingly, `yield await promise` vs. `yield promise` in an `async` generator is **not** like `return await promise` vs. `return promise` in an `async` function, at least [not as far as experimentation shows](https://jsfiddle.net/tjcrowder/L85qnc4y/). I'll have to dig into the spec at some point to see why not... – T.J. Crowder Mar 10 '21 at 17:14
  • Well as per `await` and `try`, `catch` one can always do like `yield promise.then().catch(_ => "boom")`. No? I just don't like this `await` abstraction. I feel like it's a step backwards to imperative coding in a language trying to become functional. It's just that, here we can avoid only half of it :) – Redu Mar 10 '21 at 17:15
  • 1
    @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! – T.J. Crowder Mar 10 '21 at 17:18
  • 1
    @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.) – T.J. Crowder Mar 10 '21 at 17:19