跳到內容

ch01-isolate-model

取得並執行

這個範例可以獨立 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

說明

Companion example for docs/01-why-workers.md.

Four endpoints, each demonstrating one property of the V8 isolate execution model.

Terminal window
npm install
npm run dev
RouteDemonstrates
GET /isolateModule scope state survives across requests within one isolate
GET /clockDate.now() is frozen during execution — production only
GET /cpu?n=200000000CPU-bound work is what you pay for; exceeds the Free plan 10ms limit
GET /wait?ms=5000Wall-clock waiting costs almost no CPU time

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.515s

Two 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 start
Uncaught Error: Disallowed operation called within global scope.
Asynchronous I/O (ex: fetch() or connect()), setting a timeout, and
generating 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"]
}