Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Async Generators

Async generators combine generators with async/await — they yield promises that are automatically awaited by the consumer. The async wrapper composes naturally with genfunc:

(async (genfunc fetch-pages
  :args (:string url)
  :body
  (let page 1)
  (while true
    (bind response (await (fetch (template url "?page=" page))))
    (bind data (await (response:json)))
    (if (= data:results:length 0) (return))
    (yield data:results)
    (+= page 1))))
async function* fetchPages(url) {
  if (typeof url !== "string")
    throw new TypeError(
      "fetch-pages: arg 'url' expected string, got " + typeof url);

  let page = 1;
  while (true) {
    const response = await fetch(`${url}?page=${page}`);
    const data = await response.json();
    if (data.results.length === 0) return;
    yield data.results;
    page += 1;
  }
}

for-await-of

Consume async iterables with for-await-of:

(for-await-of results (fetch-pages "/api/users")
  (for-of user results
    (process user)))

Each iteration awaits the next value. for-await-of can only appear inside an async function or at the top level of a module.

When Async Generators Shine

  • Paginated APIs — fetch pages until empty, yielding each page’s data
  • Server-sent events — yield events as they arrive from a stream
  • Line-by-line file reading — yield each line without loading the whole file
  • Any async data source with unknown length

The Alternative

For known-length async operations, Promise:all is simpler:

(bind all-pages (await (Promise:all
  (Array:from (obj :length 5) (fn (:any _ :number i)
    (fetch (template "/api/users?page=" (+ i 1))))))))

Async generators add value when the total count is unknown or when you want to process items as they arrive rather than waiting for all of them.