ch20-cron
在 GitHub 上檢視·4 個檔案·3.2 KB
取得並執行
這個範例可以獨立 clone 執行,不依賴其他章節。
npx degit mrsuner/cloudflare-serverless-platform-manuals/examples/ch20-cron
cd ch20-cron
npm install可用指令
npm run dev # wrangler dev
npm run typecheck # tsc --noEmit
npm run cf-typegen # wrangler types --env-interface CloudflareBindings說明
ch20 — Cron Triggers
Section titled “ch20 — Cron Triggers”Companion example for docs/20-cron-triggers.md.
npm install && npm run devB=localhost:8787curl -s "$B/clear"curl -s "$B/cdn-cgi/handler/scheduled?format=json"curl -s "$B/cdn-cgi/handler/scheduled?cron=0+15+1+*+*&format=json"curl -s "$B/cdn-cgi/handler/scheduled?cron=*%2F3+*+*+*+*&time=1745856238000&format=json"curl -s "$B/runs"Verified findings
Section titled “Verified findings”2026-08-01 · wrangler@4.114.0 · workerd@1.20260722.1
The old local-testing paths are gone
Section titled “The old local-testing paths are gone”GET /cdn-cgi/handler/scheduled -> okGET /cdn-cgi/handler/scheduled?format=json -> {"outcome":"ok","noRetry":false}GET /__scheduled -> 404wrangler dev --test-scheduled is likewise absent from current docs.
Without ?cron=, controller.cron is an empty string
Section titled “Without ?cron=, controller.cron is an empty string”cron='' scheduledTime=1785560195010 -> branch: othercron='0 15 1 * *' scheduledTime=1785560195018 -> branch: monthlycron='*/3 * * * *' scheduledTime=1745856238000 -> branch: frequentNot the first configured cron — empty. Any switch (controller.cron) falls to
default during local testing unless you pass ?cron=. ?time= sets
scheduledTime (the third row is the timestamp I passed in).
ScheduledController really contains
Section titled “ScheduledController really contains”{"ownProps":["cron","scheduledTime"], "proto":["noRetry","constructor"], "type":null}interface ScheduledController { readonly scheduledTime: number; readonly cron: string; noRetry(): void;}controller.type is documented but does not exist — absent from both the
generated types and the runtime. noRetry() is the reverse: present in types
and runtime, missing from the Scheduled Handler API reference page.
ctx in a scheduled handler has the same shape as in fetch, and waitUntil
does work here (unlike inside a Durable Object, chapter 14).
?format=json reports the recorded outcome
Section titled “?format=json reports the recorded outcome”after controller.noRetry() -> {"outcome":"ok","noRetry":true}handler throws -> {"outcome":"exception","noRetry":false}This is what the dashboard’s Cron Events table records, so error handling can be validated locally.
原始碼
wrangler.jsonc
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "ch20-cron",
"main": "src/index.ts",
"compatibility_date": "2026-07-24",
"observability": { "enabled": true },
"triggers": {
"crons": [
"*/3 * * * *", // every 3 minutes
"0 15 1 * *", // 15:00 UTC on the 1st
"59 23 LW * *", // last weekday of the month
"0 18 * * friL" // last Friday of the month
]
}
}package.json
{ "name": "ch20-cron", "private": true, "type": "module",
"scripts": { "dev": "wrangler dev", "typecheck": "tsc --noEmit",
"cf-typegen": "wrangler types --env-interface CloudflareBindings" },
"devDependencies": { "typescript": "^5.9.0", "wrangler": "^4.114.0" } }src/index.ts
// ---------------------------------------------------------------------------
// Chapter 20 — Cron Triggers.
// ---------------------------------------------------------------------------
const runs: Array<Record<string, unknown>> = [];
let noRetryNext = false;
let throwNext = false;
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/runs") return Response.json({ count: runs.length, runs });
if (url.pathname === "/clear") { runs.length = 0; return Response.json({ ok: true }); }
if (url.pathname === "/noretry") { noRetryNext = url.searchParams.get("on") === "1"; return Response.json({ noRetryNext }); }
if (url.pathname === "/throw") { throwNext = true; return Response.json({ throwNext }); }
return new Response("try /runs /clear, and POST /cdn-cgi/handler/scheduled\n", { status: 404 });
},
async scheduled(
controller: ScheduledController,
env: CloudflareBindings,
ctx: ExecutionContext,
): Promise<void> {
const c = controller as ScheduledController & { noRetry?: () => void; type?: string };
runs.push({
cron: controller.cron,
scheduledTime: controller.scheduledTime,
scheduledTimeIso: new Date(controller.scheduledTime).toISOString(),
// `type` and `noRetry` are barely documented — probe them.
type: c.type ?? null,
ownProps: Object.getOwnPropertyNames(controller),
proto: Object.getOwnPropertyNames(Object.getPrototypeOf(controller)),
hasNoRetry: typeof c.noRetry === "function",
// ctx in a scheduled handler
ctxOwn: Object.getOwnPropertyNames(ctx),
ctxProto: Object.getOwnPropertyNames(Object.getPrototypeOf(ctx)),
});
// Demonstrate branching on which schedule fired.
switch (controller.cron) {
case "*/3 * * * *":
runs.push({ branch: "frequent" });
break;
case "0 15 1 * *":
runs.push({ branch: "monthly" });
break;
default:
runs.push({ branch: "other", cron: controller.cron });
}
if (noRetryNext) { c.noRetry?.(); runs.push({ calledNoRetry: true }); }
if (throwNext) { throwNext = false; throw new Error("deliberate scheduled failure"); }
},
} satisfies ExportedHandler<CloudflareBindings>;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"] }