Skip to main content

Timers & Async Operations Guide

Learn how to use timers and asynchronous operations in your Invoke functions.

Overview

export default async function handler(req, res) {
// Built-in sleep helper (Invoke-specific)
await sleep(1000)

res.json({ delayed: true, ts: Date.now() })
}

setTimeout

Execute code after a delay:

export default function handler(req, res) {
setTimeout(() => {
console.log('Executed after 2 seconds')
}, 2000)

res.send('Timer set')
}

With Async/Await

function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}

export default async function handler(req, res) {
console.log('Start')
await delay(2000)
console.log('After 2 seconds')
res.send('Done')
}

setInterval

Execute code repeatedly at intervals:

export default function handler(req, res) {
let count = 0

const interval = setInterval(() => {
count++
console.log('Count:', count)

if (count >= 5) {
clearInterval(interval)
res.send('Completed 5 iterations')
}
}, 1000)
}

setImmediate

Execute on next event loop tick:

export default function handler(req, res) {
console.log('1')

setImmediate(() => {
console.log('3 - Immediate')
})

console.log('2')
// Output: 1, 2, 3 - Immediate
res.send('Done')
}

sleep() — Invoke Global (Bun) / Task.Delay (.NET)

Promise-based delay:

export default async function handler(req, res) {
console.log('Start:', new Date().toISOString())

await sleep(1000)
console.log('After 1 second')

await sleep(2000)
console.log('After 3 seconds total')

res.json({ message: 'Completed', timestamp: new Date().toISOString() })
}

Timers/Promises API

import { setTimeout as setTimeoutPromise } from 'timers/promises'

export default async function handler(req, res) {
// Promise-based delay
await setTimeoutPromise(1000)
console.log('After 1 second')

// Delay with a return value
const result = await setTimeoutPromise(1000, 'delayed value')
console.log(result) // 'delayed value'

res.send('Done')
}

Async Interval

import { setInterval as setIntervalAsync } from 'timers/promises'

export default async function handler(req, res) {
const messages = []
let count = 0

for await (const _ of setIntervalAsync(1000, Date.now())) {
messages.push(`Tick ${++count} at ${new Date().toISOString()}`)
if (count >= 5) break
}

res.json({ messages })
}

AbortController with Timers

Cancel timers using AbortController:

import { setTimeout as setTimeoutPromise } from 'timers/promises'

export default async function handler(req, res) {
const controller = new AbortController()

// Cancel after 3 seconds
setTimeoutPromise(3000).then(() => controller.abort())

try {
await setTimeoutPromise(10000, 'completed', { signal: controller.signal })
res.send('Completed 10 seconds')
} catch (error) {
if (error.name === 'AbortError') {
res.send('Cancelled after 3 seconds')
} else {
throw error
}
}
}

Common Patterns

Retry with Exponential Backoff

async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url)
if (response.ok) return await response.json()

if (i < maxRetries - 1) await sleep(Math.pow(2, i) * 1000)
} catch (error) {
if (i === maxRetries - 1) throw error
await sleep(Math.pow(2, i) * 1000)
}
}
}

export default async function handler(req, res) {
const data = await fetchWithRetry('https://api.example.com/data')
res.json(data)
}

Timeout Wrapper

async function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), ms))
return Promise.race([promise, timeout])
}

export default async function handler(req, res) {
try {
const data = await withTimeout(
fetch('https://api.example.com/slow').then(r => r.json()),
5000
)
res.json(data)
} catch (error) {
res.status(408).json({ error: 'Request timeout' })
}
}

Debounce

function debounce(func, wait) {
let timeout
return function (...args) {
clearTimeout(timeout)
timeout = setTimeout(() => func.apply(this, args), wait)
}
}

export default async function handler(req, res) {
const processRequest = debounce(async data => {
console.log('Processing:', data)
await kv.set('last:request', data)
}, 1000)

processRequest(req.body)
res.send('Request queued')
}

Throttle

function throttle(func, limit) {
let inThrottle
return function (...args) {
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
setTimeout(() => (inThrottle = false), limit)
}
}
}

export default function handler(req, res) {
const logRequest = throttle(() => {
console.log('Request logged at', new Date().toISOString())
}, 5000)

logRequest()
res.send('OK')
}

Polling

async function poll(fn, validate, interval = 1000, maxAttempts = 30) {
for (let i = 0; i < maxAttempts; i++) {
const result = await fn()
if (validate(result)) {
return result
}
await sleep(interval)
}
throw new Error('Max polling attempts exceeded')
}

export default async function handler(req, res) {
try {
const result = await poll(
() => fetch('https://api.example.com/job/123').then(r => r.json()),
data => data.status === 'completed',
2000, // Check every 2 seconds
15 // Max 30 seconds
)

res.json(result)
} catch (error) {
res.status(408).json({ error: 'Job did not complete in time' })
}
}

Rate Limiting with Timers

export default async function handler(req, res) {
const clientId = req.headers['x-client-id'] || 'anonymous'
const key = `ratelimit:${clientId}`

const requests = (await kv.get(key)) || []
const now = Date.now()
const recentRequests = requests.filter(time => time > now - 60000)

if (recentRequests.length >= 10) {
const resetTime = new Date(recentRequests[0] + 60000).toISOString()
return res.status(429).json({ error: 'Rate limit exceeded', resetAt: resetTime })
}

recentRequests.push(now)
await kv.set(key, recentRequests, 60000)
res.json({ success: true })
}

Best Practices

1. Clean Up Timers

// ✅ Always clear timers to prevent leaks
const timeout = setTimeout(() => {}, 5000)
clearTimeout(timeout)

const interval = setInterval(() => {}, 1000)
clearInterval(interval)

2. Use sleep() / Task.Delay for Simple Delays

// ❌ Verbose
await new Promise(resolve => setTimeout(resolve, 1000))

// ✅ Simple
await sleep(1000)

3. Handle Long-Running Operations

// Set a timeout on outgoing requests
const controller = new AbortController()
setTimeout(() => controller.abort(), 30000)
await fetch(url, { signal: controller.signal })

4. Avoid Blocking

// ❌ Blocks the event loop
for (let i = 0; i < 1_000_000; i++) {
/* heavy work */
}

// ✅ Yield to the event loop periodically
for (let i = 0; i < 1_000_000; i++) {
if (i % 1000 === 0) await sleep(0)
}

Next Steps