Search code examples
javascriptgeneratores6-promise

Can generator use async functions?


I am fetching data from backend service, 20 results at a time. I would like to hide this implementation detail and create a generator that would keep returning records for as long as I need them (while they are available).

Naive implementation:

function* getEndlessRecords(fetchingFunction) {
  const batchSize = 20;

  // endless loop:
  for (let offset = 0; true; offset += batchSize) {
    fetchingFunction(offset, batchSize)
      .then(records => {
        for (let i=0; i < records.length; i++) {
          yield records[i]; // THIS DOESN'T WORK!!!
        }
      })
  }
}

(there might be typos - this is simplified code)

I understand why this doesn't work (yield works on innermost function), however I can't seem to find a nice way to create a generator around the async functions.

Is it possible for generator to consume output from async functions?


Solution

  • As of ES2018, you could use an async generator function:

    async function* getEndlessRecords(fetchingFunction) {
      const batchSize = 20;
    
      // endless loop:
      for (let offset = 0; true; offset += batchSize) {
        const records = await fetchingFunction(offset, batchSize);
        for (let i=0; i < records.length; i++) {
          yield records[i];
        }
      }
    }
    

    You'd consume that in an async function using for-await-of (not for-of):

    for await (const value of getEndlessRecords(/*...*/)) {
        // Do something with `value`
    }
    

    ...or just by calling its next method and awaiting the result:

    let g = getEndlessRecords(/*...*/);
    let result;
    while (!(result = await g.next()).done) {
        console.log(result.value);
    }
    

    ...or of course, in a non-async function, you'd use then on the result of g.next().

    Prior to ES2018's async generator function syntax, you'd have to hand-code the generator rather than using function* syntax. Doing so arguably-incorrectly (not allowing for any extensions to %GeneratorPrototype%) is fairly easy. Doing so correctly is fairly awkward, since %GeneratorPrototype% doesn't have a publicly-accessible symbol and you have to go discover it.