ðŸŠī\zansh.in\js

Play with the controls to see how the code works. All code is present in the page, but some scrollback may be necessary to grok everything.

# finite state-machine

2 min read

Banish status booleans (or derive them from something predictable) with a finite state-machine.

# nextState

const chart = {
idle: { REQUEST: 'loading' },
loading: { DONE: 'complete' }
}
const currentState = 'idle'
function sendRequest() {
const eventName = 'REQUEST'
const nextState =
chart?.[currentState]?.[eventName]
return nextState
}
function sendDone() {
const eventName = 'DONE'
const nextState =
chart?.[currentState]?.[eventName]
return nextState
}

# send()

let currentState = 'idle'
function send(eventName) {
const nextState =
chart?.[currentState]?.[eventName]
if (nextState !== undefined) {
return (currentState = nextState)
}
}

# stack / fifo

let count = 0
const fifo = []
const push = () => fifo.push(++count)
const shift = () => fifo.shift()

# history

const send = StateHistory('idle', chart)
function StateHistory(
currentState,
chart
) {
const history = ['', currentState]
return eventName => {
const nextState =
chart?.[currentState]?.[eventName]
if (nextState === undefined) {
return
}
currentState = nextState
history.push(currentState)
history.shift()
return history
}
}

# StateMachine()

function handleTransition({ from, to }) {
console.log(`${from}->${to}`)
}
const { current, send } =
StateMachine(
{
idle: { REQUEST: 'loading' },
loading: {
DONE: 'complete',
FAIL: 'error'
}
},
handleTransition
)
function StateMachine(
chart,
onTransition = () => {}
) {
const states = Object.keys(chart)
const initial = ['', states[0]]
const current = () => [...history].pop()
let history = [...initial]
return {
send,
current,
reset: () => (history = [...initial])
}
function send(eventName) {
const currentState = current()
const nextState =
chart?.[currentState]?.[eventName]
if (
nextState &&
nextState !== currentState
) {
history.push(nextState)
history.shift()
const [from, to] = history
onTransition({ from, to })
}
}
}

# hold()

const pause = () =>
hold(1).then(n =>
console.log(`took ${n} seconds`)
)
function hold(n) {
return new Promise(resolve =>
setTimeout(resolve, n * 1000, n)
)
}

# StateMachine() + CustomEvent

const { current, send } =
StateMachine(
{
idle: { REQUEST: 'loading' },
loading: {
DONE: 'complete',
FAIL: 'error'
}
},
({ from, to }) => {
const name = `${from}->${to}`
const event = new CustomEvent(name)
dispatchEvent(event)
}
)
addEventHandler('idle->loading', () => {
console.log('loading...')
hold(1)
.then(() => send('DONE'))
.catch(() => send('FAIL'))
})
addEventHandler('loading->complete', () =>
console.log('✅ done!')
)

Google it:

ðŸŒļ addEventHandler 🌚 addition assignment 🌷 Array push ðŸŒŧ Array shift 🌞 closures ðŸŒđ CustomEvent 💐 destructuring assignment ðŸŒļ dispatchEvent 🌚 expression assignment 🌷 hoisting ðŸŒŧ optional chaining 🌞 Promise ðŸŒđ property accessors 💐 setTimeout ðŸŒļ spread rest