Skip to content

Instantly share code, notes, and snippets.

What would you like to do?

An interesting pattern I stumbled on recently thanks to @davidmarkclements

Iterators can be used to encode async state machines if you have a series of conditions you need to check after each async step.

For example, when doing UDP holepunching we do 5-10 async things overall, but at each async step we wanna check if.

  1. Holepunching is done (ie we have connection)
  2. And/or if the holepunch has been aborted etc

Normally I'd write that like this:

async function punch (retry = true) {
  const c = await connect()
  if (done()) return

  const e = await exchangeThings(c)
  if (done()) return

  await holepunch(e)
  if (done()) return

  if (retry) return punch(false)

You cannot really put the done check inside the functions because await add an implicit async tick, so then you might continue when you shouldn't - so it's kinda verbose.

But David showed me how to do this with an iterator instead which is quite interesting

function * punch (retry = true) {
  const c = yield connect()
  const e = yield exchangeThings(e)
  yield holepunch(e)
  // yield * inlines the iterator
  if (retry) yield * punch(false)

const ite = punch()
let next =
while (!next.done) {
  const val = await next.value
  // Just put your state machine "static" conditions here!
  if (done()) return
  next =

That's all! Thought you might find this useful as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment