ch01-isolate-model
對應 01. 為什麼是 Workers:isolate 不是「更快的 Lambda」
在 GitHub 上檢視·4 個檔案·4.6 KB
取得並執行
這個範例可以獨立 clone 執行,不依賴其他章節。
npx degit mrsuner/cloudflare-serverless-platform-manuals/examples/ch01-isolate-model
cd ch01-isolate-model
npm install可用指令
npm run dev # wrangler dev
npm run deploy # wrangler deploy
npm run typecheck # tsc --noEmit
npm run cf-typegen # wrangler types說明
ch01 — Making the isolate model visible
Section titled “ch01 — Making the isolate model visible”Companion example for docs/01-why-workers.md.
Four endpoints, each demonstrating one property of the V8 isolate execution model.
npm installnpm run devEndpoints
Section titled “Endpoints”| Route | Demonstrates |
|---|---|
GET /isolate | Module scope state survives across requests within one isolate |
GET /clock | Date.now() is frozen during execution — production only |
GET /cpu?n=200000000 | CPU-bound work is what you pay for; exceeds the Free plan 10ms limit |
GET /wait?ms=5000 | Wall-clock waiting costs almost no CPU time |
Verified behaviour
Section titled “Verified behaviour”Tested against wrangler@4.114.0, compatibility_date: 2026-07-24, on 2026-07-27.
$ for i in 1 2 3; do curl -s localhost:8787/isolate; echo; done{"isolateId":"3805b103-...","requestsServedByThisIsolate":1}{"isolateId":"3805b103-...","requestsServedByThisIsolate":2}{"isolateId":"3805b103-...","requestsServedByThisIsolate":3}
$ time curl -s "localhost:8787/wait?ms=1500"{"waitedMs":1500}real 0m1.515sTwo things this example proves the hard way
Section titled “Two things this example proves the hard way”1. Global scope forbids more than I/O.
const ISOLATE_ID = crypto.randomUUID(); // ❌ worker fails to startUncaught Error: Disallowed operation called within global scope.Asynchronous I/O (ex: fetch() or connect()), setting a timeout, andgenerating random values are not allowed within global scope.Random value generation is disallowed alongside I/O and timers. Hence the lazy
identify() helper in src/index.ts.
2. wrangler dev does not reproduce the frozen clock.
Docs state Date.now() “returns the time of the last I/O. It does not advance
during code execution.” That is a production behaviour. Locally:
$ curl -s localhost:8787/clock{"deltaWithoutIo":9,"deltaAfterIo":102}deltaWithoutIo is 9, not 0. Deploy before drawing conclusions about
anything security- or limit-related.
原始碼
wrangler.jsonc
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "ch01-isolate-model",
"main": "src/index.ts",
"compatibility_date": "2026-07-24",
"observability": {
"enabled": true
}
}package.json
{
"name": "ch01-isolate-model",
"private": true,
"type": "module",
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy",
"typecheck": "tsc --noEmit",
"cf-typegen": "wrangler types"
},
"devDependencies": {
"typescript": "^5.9.0",
"wrangler": "^4.114.0"
}
}src/index.ts
// ---------------------------------------------------------------------------
// Chapter 01 — Making the isolate model visible.
//
// Module scope runs ONCE per isolate, not once per request, and counts
// against the 1 second startup CPU budget.
//
// NOTE: the runtime forbids async I/O, timers AND random value generation in
// global scope. `const ID = crypto.randomUUID()` here fails to start with:
// "Disallowed operation called within global scope."
// So the identity is initialised lazily, on the first request this isolate
// happens to serve. That laziness is itself the lesson.
// ---------------------------------------------------------------------------
let isolateId: string | undefined;
let isolateFirstSeenAt: number | undefined;
let requestsServedByThisIsolate = 0;
function identify() {
isolateId ??= crypto.randomUUID();
isolateFirstSeenAt ??= Date.now();
return { isolateId, isolateFirstSeenAt };
}
export default {
async fetch(request: Request): Promise<Response> {
requestsServedByThisIsolate++;
const url = new URL(request.url);
switch (url.pathname) {
case "/isolate":
return handleIsolate();
case "/clock":
return handleClock();
case "/cpu":
return handleCpu(Number(url.searchParams.get("n") ?? 1_000_000));
case "/wait":
return handleWait(Number(url.searchParams.get("ms") ?? 500));
default:
return new Response(
[
"Chapter 01 — isolate model demo",
"",
" GET /isolate isolate identity + reuse counter",
" GET /clock Date.now() does not advance during execution",
" GET /cpu?n=10000000 CPU-bound work (this is what you pay for)",
" GET /wait?ms=2000 wall-clock wait (nearly free)",
"",
].join("\n"),
{ status: 404, headers: { "content-type": "text/plain; charset=utf-8" } },
);
}
},
} satisfies ExportedHandler;
// --- 1. Isolate reuse ------------------------------------------------------
// Refresh this repeatedly. The isolate id stays the same while the same
// isolate serves you, then changes when you land on a different one.
function handleIsolate(): Response {
const { isolateId, isolateFirstSeenAt } = identify();
return Response.json({
isolateId,
isolateFirstSeenAt: new Date(isolateFirstSeenAt).toISOString(),
requestsServedByThisIsolate,
});
}
// --- 2. The clock does not advance during execution ------------------------
// `before` and `after` are identical: no I/O happened in between.
// `afterIo` differs, because awaiting a fetch is an I/O boundary.
async function handleClock(): Promise<Response> {
const before = Date.now();
let sink = 0;
for (let i = 0; i < 5_000_000; i++) sink += i;
const after = Date.now();
await fetch("https://cloudflare.com/cdn-cgi/trace");
const afterIo = Date.now();
return Response.json({
sink,
before,
after,
deltaWithoutIo: after - before, // always 0
afterIo,
deltaAfterIo: afterIo - before, // non-zero
});
}
// --- 3. CPU-bound work is what you actually pay for ------------------------
// On the Free plan, a large enough `n` exceeds the 10ms CPU limit and the
// request is terminated with "Error 1102: Worker exceeded CPU time limit".
function handleCpu(iterations: number): Response {
let sink = 0;
for (let i = 0; i < iterations; i++) sink += Math.sqrt(i);
return Response.json({ iterations, sink });
}
// --- 4. Waiting is nearly free --------------------------------------------
// This blocks for `ms` of wall time but consumes almost no CPU time.
// Compare the dashboard "CPU Time" chart against the observed wall duration.
async function handleWait(ms: number): Promise<Response> {
await scheduler.wait(ms);
return Response.json({ waitedMs: ms });
}tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"lib": ["esnext"],
"module": "esnext",
"moduleResolution": "bundler",
"types": ["./worker-configuration.d.ts"],
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"isolatedModules": true
},
"include": ["src/**/*.ts", "worker-configuration.d.ts"]
}