Async inside one call
db is synchronous. A read or write returns before the next line runs, with no
event-loop hop. fetch, timers, and promises use ordinary JavaScript behavior.
You can await a promise, chain .then, or return a Promise from an async
handler. queueMicrotask uses the engine's microtask queue, as in a browser.
export default {
async fetch(request, env, ctx) {
const upstream = await fetch("https://api.example.com/data");
db.events.insert({ at: Date.now() });
ctx.waitUntil(fetch("https://hooks.example.com/seen", { method: "POST" }));
return Response.json(await upstream.json());
},
};
ctx.waitUntil(promise) does not run work after the response. Every waitUntil
promise is awaited after your handler returns and before the response is sent.
setTimeout and setInterval return a numeric id for clearTimeout and
clearInterval. Timers use the call's CPU and wall-time budget and are cleared
when the call ends, so they cannot fire after it finishes.
Calls run concurrently
Each HTTP request, cron run, queue delivery, or direct function call is a
separate call. Many calls for the same app can run concurrently: 512 by default,
or the app's cf_concurrent_limit, available from GET /v1/_limits. A call over
that limit is refused with HTTP 429. A call beyond the worker's queue depth is
refused with HTTP 503. See Limits for related limits and errors.
State is not shared safely
Module-level variables and globalThis persist on the worker thread that runs a
call. They are not scoped to the call; see State between calls.
Treat state outside db, KV, or records as a cache that may be stale, empty,
or shared with another concurrent call.
One object, one call at a time
Calls to a durable object queue on a mailbox and run one at a time, so its in-memory fields can be read and written without a lock. Use a durable object when two calls must never interleave on the same data.