Skip to content

Instantly share code, notes, and snippets.

@mafintosh
Last active June 21, 2022 05:50
Show Gist options
  • Save mafintosh/9cfb78ec8f2e4989ecd76d1b005d866e to your computer and use it in GitHub Desktop.
Save mafintosh/9cfb78ec8f2e4989ecd76d1b005d866e to your computer and use it in GitHub Desktop.

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 = ite.next()
while (!next.done) {
  const val = await next.value
  // Just put your state machine "static" conditions here!
  if (done()) return
  next = ite.next(val)
}

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