skip to Main Content

When using for await...of to iterate over an async generator, the final value (return value) seems to be consumed and inaccessible afterward. Here’s a minimal example:

async function* exampleGenerator() {
  let i = 1;

  while (true) {
    if (something) {
      return 'finished';
    }

    yield i++;
  }
}

async function main() {
  const generator = exampleGenerator();

  for await (const value of generator) {
    console.log(value); // Logs 1, 2, 3...
  }

  const final = await generator.next();
  console.log(final.value); // undefined, expected 'finished'
}

main();

How can I access the final value ('finished') after using for await...of?
Is there a way to retrieve it while keeping the simplicity of the for loop?

2

Answers


  1. In short, you can’t use a for ... of loop if you need the return value of the iterator. A for loop will, as you described, "consume" it.

    You can change your looping strategy to call the iterator directly:

    async function main() {
      const generator = exampleGenerator();
      
      let emergencyBrake = 1000;
      while (true) {
        const { done, value } = await generator.next();
        if (done) {
          console.log("final value: " + value);
          break;
        }
    
        console.log(value);
        if (--emergencyBrake < 0) break;
      }
    }
    

    Explanation

    A generator function returns an object that adheres to the iterator protocol. Here’s the relevant bit from the MDN documentation:

    done Optional
    A boolean that’s false if the iterator was able to produce the next value in the sequence. (This is equivalent to not specifying the done property altogether.)

    Has the value true if the iterator has completed its sequence. In this case, value optionally specifies the return value of the iterator.

    value Optional
    Any JavaScript value returned by the iterator. Can be omitted when done is true.

    A for ... of loop exits as soon as done is true:

    A for…of loop exits when the iterator has completed (the next() result is an object with done: true)

    So your final value is included in the final response from your generator function, but is ignored by the for loop. Calling next() again will return undefined for the value because the generator has already finished.

    Login or Signup to reply.
  2. You can write a little helper withReturn that includes the return value –

    function *mygen() {
      yield 1
      yield 2
      return 3
    }
    
    function *withReturn(it) {
      yield (yield *it)
    }
    
    for (const v of withReturn(mygen()))
      console.log("value", v)
      
    // value 1
    // value 2
    // value 3

    If more yield statements occur after the return, they will not be included –

    function *mygen() {
      yield 1
      yield 2
      return 3
      yield 4
      yield 5
    }
    
    function *withReturn(it) {
      yield (yield *it)
    }
    
    for (const v of withReturn(mygen()))
      console.log("value", v)
      
    // value 1
    // value 2
    // value 3

    Here’s the async..await version –

    async function *mygen() {
      await sleep(1000)
      yield 1
      await sleep(1000)
      yield 2
      await sleep(1000)
      return 3
    }
    
    async function *withReturn(it) {
      yield await (yield *it)
    }
    
    async function main() {
      for await (const v of withReturn(mygen()))
        console.log("value", v)
      return "done"
    }
    
    function sleep(ms) {
      return new Promise(r => setTimeout(r, ms))
    }
    
    main().then(console.log).catch(console.error)
      
    // value 1
    // value 2
    // value 3
    // done

    Note, if mygen does not have a return value, an undefined will be yielded –

    function *mygen() {
      yield 1
      yield 2
      // no return statement here!
    }
    
    function *withReturn(it) {
      yield (yield *it)
    }
    
    for (const v of withReturn(mygen()))
      console.log("value", v)
      
    // value 1
    // value 2
    // value undefined

    If this behavior is undesirable, you can conditionally yield the return value, if it is present –

    function *mygen() {
      yield 1
      yield 2
    }
    
    function *withReturn(it) {
      const ret = (yield *it)
      if (ret !== undefined)
        yield ret
    }
    
    for (const v of withReturn(mygen()))
      console.log("value", v)
      
    // value 1
    // value 2
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search