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.
- Holepunching is done (ie we have connection)
- 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