Build a local AI feature
Add timeout and cancellation
Stop work that has exceeded the feature budget or is no longer needed, while distinguishing cancellation from service failure.
A local request can still hang or take too long. Local does not mean instant. A huge context or a cold load on a busy machine can blow a reasonable UI budget.
Pass an abort signal to fetch():
const signal = AbortSignal.timeout(8000)
const response = await fetch('http://localhost:11434/api/chat', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
signal,
})
Eight seconds is an example, not a universal default. Measure the real model and choose a budget that matches the feature. A background nightly job can tolerate minutes. A settings panel should not.
Cancellation has another use. If the user closes a view or replaces the input, the old result may no longer matter. Abort the pending request so a stale answer cannot update the interface later. Race conditions show up often when streaming is enabled.
Handle these outcomes separately in logs:
- user or lifecycle cancellation
- timeout
- connection failure
- HTTP error
- invalid model output
They may share the same deterministic fallback in the UI, but they need different diagnostics. “Timeout” suggests a smaller model or shorter prompt. “Connection failure” suggests Ollama is down.
In Node 18+, AbortSignal.timeout(ms) is the simple path. For manual cancellation, create an AbortController, pass controller.signal, and call controller.abort() when the user navigates away.
My advice is to surface timeout copy that mentions the feature, not the stack. “Summary is taking longer than expected” beats “fetch aborted.”
Catch abort errors explicitly:
try {
const response = await fetch(url, { method: 'POST', body, signal })
} catch (error) {
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
return fallbackSummary(activity)
}
throw error
}
You should hit the fallback on timeout without treating it like an unknown crash.
Try this on your own project: set an artificially low timeout once, confirm the fallback sentence appears, then restore the real budget you measured.
Lesson completed