Vue3 composable poller
I found myself in the situation of having to refresh frontend data every few seconds to make people wait for the end of an asynchronous remote procedure. The optimal solution for this type of problem is certainly a WebSocket, this with API Gateway is done by configuring its specific functionality, it would not be possible to forcibly manage it from within a Lambda function, it could not remain active for more than 15 minutes (and it would unnecessarily expensive).
Wanting to use a simple polling solution, an HTTP request sent every few seconds, I created a composable function to be able to reuse this functionality in the application:
export default function (fn: Function, seconds: number = 5) {
let timer: NodeJS.Timeout|undefined
let counter = 0
const config = useAppConfig()
async function tick() {
counter++
await Promise.resolve(fn(counter))
}
async function start() {
if (timer) {
stop()
}
if (!config.polling) {
return
}
counter = 0
await tick()
timer = setInterval(() => tick(), seconds * 1000)
}
function stop() {
if (!timer) {
return
}
clearInterval(timer)
timer = undefined
}
return {
start,
tick,
stop,
}
}
I didn’t just need to start the polling and that’s it, I also needed to control when it was started or stopped depending of the situation, for example to mount and unmount the component:
<script setup>
const api = useApi()
const processes = ref([])
const poller = usePoller(async () => {
processes.value = await api.listProcesses()
}, 15)
onMounted(() => poller.start())
onUnmounted(() => poller.stop())
</script>
In some cases it was not necessary to start the polling when the component was mounted, but at least an initial data recovery was necessary. To do this I used a “tick” function that forces the poller to run one round, whether it is running or not:
<script setup>
const api = useApi()
const processes = ref([])
const poller = usePoller(async () => {
processes.value = await api.listProcesses()
}, 15)
onMounted(() => poller.tick()) // <----
onUnmounted(() => poller.stop())
const startPoller = async () => {
await poller.start()
}
const stopPoller = async () => {
await poller.stop()
}
</script>
If the poller is already running and the data changes due to a modification of the frontend itself, it would be a good idea to immediately refresh the data instead of waiting for the next poller execution.
To do this I linked some application events to the execution of the “tick” function:
<process-list :processes="processes" @stopped="poller.tick()" @started="poller.tick()" />
This solution can have many approaches, not to be abused so as not to waste HTTP request executions