关于 cloudflare-workflows
cloudflare-workflows 是一项在 Cloudflare Workers 上构建持久化工作流的技能,介绍步骤执行、重试、休眠、事件等待、测试及已记录问题的应对方法。
主要功能
- 01支持重试和超时的工作流步骤
- 02休眠与等待事件模式
- 03Vitest 测试指南
- 04重启期间的状态与副作用处理
- 05已记录平台问题的规避方法
适用场景
- 01实现可重试步骤组成的工作流
- 02暂停工作流并在事件到达后恢复
- 03使用 Vitest 测试工作流行为
- 04避免重启后重复执行副作用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill cloudflare-workflows安装后在 Claude Code 中输入/cloudflare-workflows确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.md.claude-plugin/plugin.jsonreferences/common-issues.mdworkflow-patterns.mdrules/cloudflare-workflows.mdtemplates/basic-workflow.tsscheduled-workflow.tsworker-trigger.tsworkflow-with-events.tsworkflow-with-retries.ts详细介绍
Cloudflare Workflows
状态:已可用于生产环境 ✅(自 2025 年 4 月起正式发布) 最后更新:2026-01-09 依赖项:cloudflare-worker-base(用于 Worker 设置) 最新版本:[email protected]、@cloudflare/[email protected]
近期更新(2025 年):
- 2025 年 4 月:Workflows 正式发布——waitForEvent API、Vitest 测试、CPU 时间指标、4,500 个并发实例
- 2025 年 10 月:实例创建速率提升 10 倍(每秒 100 个),并发数提升至 10,000
- 2025 年限制:最多 1,024 个步骤,每步持久化状态 1MB(每个实例 100MB-1GB),事件载荷 1MB,CPU 时间最长 5 分钟
- 测试:cloudflare:test 模块,包含 introspectWorkflowInstance、disableSleeps、mockStepResult、mockEvent 修饰符
- 平台:等待中的实例不计入并发数,保留期限 3-30 天,子请求数 50-1,000
快速开始(5 分钟)
# 1. Scaffold project
npm create cloudflare@latest my-workflow -- --template cloudflare/workflows-starter --git --deploy false
cd my-workflow
# 2. Configure wrangler.jsonc
{
"name": "my-workflow",
"main": "src/index.ts",
"compatibility_date": "2025-11-25",
"workflows": [{
"name": "my-workflow",
"binding": "MY_WORKFLOW",
"class_name": "MyWorkflow"
}]
}
# 3. Create workflow (src/index.ts)
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const result = await step.do('process', async () => { /* work */ });
await step.sleep('wait', '1 hour');
await step.do('continue', async () => { /* more work */ });
}
}
# 4. Deploy and test
npm run deploy
npx wrangler workflows instances list my-workflow
重要:继承 WorkflowEntrypoint,使用 step 方法实现 run(),并在 wrangler.jsonc 中配置绑定
已知问题预防
此技能可预防 Cloudflare Workflows 的 12 个已记录错误。
问题 #1:waitForEvent 超时后会跳过事件(本地开发环境)
错误:waitForEvent() 超时后发送的事件会被后续的 waitForEvent() 调用忽略
环境:仅限本地开发环境(wrangler dev)——生产环境中运行正常
来源:GitHub Issue #11740
原因:这是 miniflare 中的一个错误,已在生产环境中修复(2025 年 5 月),但尚未移植到本地模拟器。超时后,该实例的事件队列会损坏。
预防方法:
- 在生产/预发布环境中测试 waitForEvent 超时场景,不要在本地开发环境中测试
- 避免串联多个预期会超时的
waitForEvent()调用
错误示例:
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
for (let i = 0; i < 3; i++) {
try {
const evt = await step.waitForEvent(`wait-${i}`, {
type: 'user-action',
timeout: '5 seconds'
});
console.log(`Iteration ${i}: Received event`);
} catch {
console.log(`Iteration ${i}: Timeout`);
}
}
}
}
// In wrangler dev:
// - Iteration 1: ✅ receives event
// - Iteration 2: ⏱️ times out (expected)
// - Iteration 3: ❌ does not receive event (BUG - event is sent but ignored)
状态:已知错误,miniflare 的修复尚未完成。
问题 #2:getPlatformProxy() 与 Workflow 绑定不兼容
错误:MiniflareCoreError [ERR_RUNTIME_FAILURE]: The Workers runtime failed to start
消息:Worker 的绑定引用了具有命名入口点的服务,但该服务没有此入口点
来源:GitHub Issue #9402
原因:wrangler 包中的 getPlatformProxy() 不支持 Workflow 绑定(与其处理 Durable Objects 的方式类似)。这会阻碍 Next.js 集成和本地 CLI 脚本运行。
预防方法:
- 选项 1:使用
getPlatformProxy()时注释掉 Workflow 绑定 - 选项 2:创建不包含 workflows 的独立
wrangler.cli.jsonc,用于 CLI 脚本 - 选项 3:直接通过已部署的 Worker 访问 Workflow 绑定,不要使用代理
// Workaround: Separate config for CLI scripts
// wrangler.cli.jsonc (no workflows)
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2025-01-20"
// workflows commented out
}
// Use in script:
import { getPlatformProxy } from 'wrangler';
const { env } = await getPlatformProxy({ configPath: './wrangler.cli.jsonc' });
状态:已知限制,计划修复(采用与 DO 类似的方式过滤 workflows)。
问题 #3:立即重定向后 Workflow 实例丢失(本地开发环境)
错误:已返回实例 ID,但查询时出现 instance.not_found
环境:仅限本地开发环境(wrangler dev)——生产环境中运行正常
来源:GitHub Issue #10806
原因:workflow.create() 后立即返回重定向,会导致请求在 Workflow 初始化完成前“软中止”(开发环境中为单线程执行)。
预防方法:使用 ctx.waitUntil() 确保 Workflow 初始化完成后再重定向:
export default {
async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const workflow = await env.MY_WORKFLOW.create({ params: { userId: '123' } });
// ✅ Ensure workflow initialization completes
ctx.waitUntil(workflow.status());
return Response.redirect('/dashboard', 302);
}
};
状态:已在近期 wrangler 版本中修复(2025 年 9 月之后),但仍建议使用此变通方法以确保兼容性。
问题 #4:Vitest 测试在 CI 环境中不可靠
错误:[vitest-worker]: Timeout calling "resolveId"
环境:CI/CD 流水线(GitLab、GitHub Actions)——本地运行正常
来源:GitHub Issue #10600
原因:@cloudflare/vitest-pool-workers 在 CI 容器中存在资源限制问题,对工作流测试的影响比其他 worker 类型更大。
预防措施:
- 在 vitest 配置中增大
testTimeout:export default defineWorkersConfig({ test: { testTimeout: 60_000 // Default: 5000ms } }); - 检查 CI 资源限制(CPU/内存)
- 如果不测试存储隔离,请使用
isolatedStorage: false - 对于关键工作流,可以考虑针对已部署的实例进行测试,而不是使用 vitest
状态:已知问题,正在调查(内部:WOR-945)。
问题 #5:本地开发中未实现 Instance 的 restart() 和 terminate()
错误:调用 instance.restart() 或 instance.terminate() 时出现 Error: Not implemented yet
环境:仅限本地开发(wrangler dev)——生产环境运行正常
来源:GitHub Issue #11312
原因:miniflare 中尚未实现 Instance 管理 API。此外,即使工作流处于休眠状态,实例状态仍显示为 running。
预防措施:在本地开发支持上线之前,请在生产或预发布环境中测试实例生命周期管理(暂停/恢复/终止)。
const instance = await env.MY_WORKFLOW.get(instanceId);
// ❌ Fails in wrangler dev
await instance.restart(); // Error: Not implemented yet
await instance.terminate(); // Error: Not implemented yet
// ✅ Works in production
状态:已知限制,本地开发支持暂无时间表。
问题 #6:I/O 必须在 step.do() 回调中执行
错误:"Cannot perform I/O on behalf of a different request"
来源:Cloudflare 运行时行为
原因:尝试从另一个请求处理程序中使用在某个请求上下文中创建的 I/O 对象。
预防措施:始终在 step.do() 回调中执行 I/O:
// ❌ Bad - I/O outside step
const response = await fetch('https://api.example.com/data');
const data = await response.json();
await step.do('use data', async () => {
return data; // This will fail!
});
// ✅ Good - I/O inside step
const data = await step.do('fetch data', async () => {
const response = await fetch('https://api.example.com/data');
return await response.json();
});
问题 #7:NonRetryableError 在开发和生产环境中的行为不同
错误:NonRetryableError 使用空消息时,在开发模式下会触发重试,但在生产环境中运行正常 环境:开发环境特有的错误 来源:GitHub Issue #10113
原因:miniflare 与生产运行时对空错误消息的处理方式不同。
预防措施:始终为 NonRetryableError 提供消息:
// ❌ Retries in dev, exits in prod
throw new NonRetryableError('');
// ✅ Exits in both environments
throw new NonRetryableError('Validation failed');
状态:已知问题,已记录解决方法。
问题 #8:休眠时内存状态丢失
错误:在 step.do() 外声明的变量在休眠/挂起后会重置为初始值
来源:Cloudflare 工作流规则
原因:当引擎检测到没有待处理的工作时,工作流会进入休眠。休眠期间,所有内存状态都会丢失。
预防措施:仅使用 step.do() 返回的状态——其他所有内容都是临时的:
// ❌ BAD - In-memory variable lost on hibernation
let counter = 0;
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
counter = await step.do('increment', async () => counter + 1);
await step.sleep('wait', '1 hour'); // ← Hibernates here, in-memory state lost
console.log(counter); // ❌ Will be 0, not 1!
}
}
// ✅ GOOD - State from step.do() return values persists
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const counter = await step.do('increment', async () => 1);
await step.sleep('wait', '1 hour');
console.log(counter); // ✅ Still 1
}
}
问题 #9:非确定性的步骤名称会破坏缓存
错误:步骤不必要地重新运行,导致性能下降 来源:Cloudflare 工作流规则
原因:步骤名称充当缓存键。使用 Date.now()、Math.random() 或其他非确定性值,会导致每次运行都生成新的缓存键。
预防措施:使用静态、确定性的步骤名称:
// ❌ BAD - Non-deterministic step name
await step.do(`fetch-data-${Date.now()}`, async () => {
return await fetchExpensiveData();
});
// Every execution creates new cache key → step always re-runs
// ✅ GOOD - Deterministic step name
await step.do('fetch-data', async () => {
return await fetchExpensiveData();
});
// Same cache key → result reused on restart/retry
问题 #10:在 step.do() 外使用 Promise.race/any 会导致不一致
错误:重启时解析的 Promise 不同,导致行为不一致 来源:Cloudflare 工作流规则
原因:步骤外的非确定性操作会在重启时再次运行,可能产生不同的结果。
预防措施:将所有非确定性逻辑放在 step.do() 内:
// ❌ BAD - Race outside step
const fastest = await Promise.race([fetchA(), fetchB()]);
await step.do('use result', async () => fastest);
// On restart: race runs again, different promise might win
// ✅ GOOD - Race inside step
const fastest = await step.do('fetch fastest', async () => {
return await Promise.race([fetchA(), fetchB()]);
});
// On restart: cached result used, consistent behavior
问题 #11:重启后副作用重复执行
错误:工作流重启后出现重复日志、指标或操作 来源:Cloudflare Workflows 规则
原因:如果工作流在执行过程中重启,step.do() 外部的代码会执行多次。
预防方法:将日志记录、指标和其他副作用放入 step.do() 中:
// ❌ BAD - Side effect outside step
console.log('Workflow started'); // ← Logs multiple times on restart
await step.do('work', async () => { /* work */ });
// ✅ GOOD - Side effects inside step
await step.do('log start', async () => {
console.log('Workflow started'); // ← Logs once (cached)
});
问题 #12:非幂等操作可能重复执行
错误:步骤超时后出现重复扣款、重复数据库写入 来源:Cloudflare Workflows 规则
原因:步骤会单独重试。如果 API 调用成功,但步骤在返回前超时,重试时会再次调用 API。
预防方法:通过存在性检查来保护非幂等操作:
// ❌ BAD - Charge customer without check
await step.do('charge', async () => {
return await stripe.charges.create({ amount: 1000, customer: customerId });
});
// If step times out after charge succeeds, retry charges AGAIN!
// ✅ GOOD - Check for existing charge first
await step.do('charge', async () => {
const existing = await stripe.charges.list({ customer: customerId, limit: 1 });
if (existing.data.length > 0) return existing.data[0]; // Idempotent
return await stripe.charges.create({ amount: 1000, customer: customerId });
});
步骤方法
step.do() - 执行工作
step.do<T>(name: string, config?: WorkflowStepConfig, callback: () => Promise<T>): Promise<T>
参数:
name- 步骤名称(用于可观测性)config(可选)- 重试配置(重试次数、超时、退避)callback- 执行工作的异步函数
**返回值:**回调函数返回的值(必须可序列化)
示例:
const result = await step.do('call API', { retries: { limit: 10, delay: '10s', backoff: 'exponential' }, timeout: '5 min' }, async () => {
return await fetch('https://api.example.com/data').then(r => r.json());
});
重要 - 序列化:
- ✅ 允许:字符串、数字、布尔值、Array、Object、null
- ❌ 禁止:Function、Symbol、循环引用、undefined
- 如果返回值无法进行 JSON 序列化,则会抛出错误
step.sleep() - 相对时长休眠
step.sleep(name: string, duration: WorkflowDuration): Promise<void>
参数:
name- 步骤名称duration- 数值(毫秒)或字符串:"second"、"minute"、"hour"、"day"、"week"、"month"、"year"(也接受复数形式)
示例:
await step.sleep('wait 5 minutes', '5 minutes');
await step.sleep('wait 1 hour', '1 hour');
await step.sleep('wait 2 days', '2 days');
await step.sleep('wait 30 seconds', 30000); // milliseconds
**注意:**恢复执行的工作流优先于新实例。休眠不计入步骤限制。
step.sleepUntil() - 休眠至指定日期
step.sleepUntil(name: string, timestamp: Date | number): Promise<void>
参数:
name- 步骤名称timestamp- Date 对象或 UNIX 时间戳(毫秒)
示例:
await step.sleepUntil('wait for launch', new Date('2025-12-25T00:00:00Z'));
await step.sleepUntil('wait until time', Date.parse('24 Oct 2024 13:00:00 UTC'));
step.waitForEvent() - 等待外部事件(2025 年 4 月 GA)
step.waitForEvent<T>(name: string, options: { type: string; timeout?: string | number }): Promise<T>
参数:
name- 步骤名称options.type- 要匹配的事件类型options.timeout(可选)- 最长等待时间(默认值:24 小时,最长:30 天)
**返回值:**通过 instance.sendEvent() 发送的事件负载
示例:
export class PaymentWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
await step.do('create payment', async () => { /* Stripe API */ });
const webhookData = await step.waitForEvent<StripeWebhook>(
'wait for payment confirmation',
{ type: 'stripe-webhook', timeout: '1 hour' }
);
if (webhookData.status === 'succeeded') {
await step.do('fulfill order', async () => { /* fulfill */ });
}
}
}
// Worker sends event to workflow
export default {
async fetch(req: Request, env: Env): Promise<Response> {
if (req.url.includes('/webhook/stripe')) {
const instance = await env.PAYMENT_WORKFLOW.get(instanceId);
await instance.sendEvent({ type: 'stripe-webhook', payload: await req.json() });
return new Response('OK');
}
}
};
超时处理:
try {
const event = await step.waitForEvent('wait for user', { type: 'user-submitted', timeout: '10 minutes' });
} catch (error) {
await step.do('send reminder', async () => { /* reminder */ });
}
WorkflowStepConfig
interface WorkflowStepConfig {
retries?: {
limit: number; // Max attempts (Infinity allowed)
delay: string | number; // Delay between retries
backoff?: 'constant' | 'linear' | 'exponential';
};
timeout?: string | number; // Max time per attempt
}
默认值:{ retries: { limit: 5, delay: 10000, backoff: 'exponential' }, timeout: '10 minutes' }
退避示例:
// Constant: 30s, 30s, 30s
{ retries: { limit: 3, delay: '30 seconds', backoff: 'constant' } }
// Linear: 1m, 2m, 3m, 4m, 5m
{ retries: { limit: 5, delay: '1 minute', backoff: 'linear' } }
// Exponential (recommended): 10s, 20s, 40s, 80s, 160s
{ retries: { limit: 10, delay: '10 seconds', backoff: 'exponential' }, timeout: '5 minutes' }
// Unlimited retries
{ retries: { limit: Infinity, delay: '1 minute', backoff: 'exponential' } }
// No retries
{ retries: { limit: 0 } }
错误处理
NonRetryableError
强制工作流立即失败,不进行重试:
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
import { NonRetryableError } from 'cloudflare:workflows';
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
await step.do('validate input', async () => {
if (!event.payload.userId) {
throw new NonRetryableError('userId is required');
}
// Validate user exists
const user = await this.env.DB.prepare(
'SELECT * FROM users WHERE id = ?'
).bind(event.payload.userId).first();
if (!user) {
// Terminal error - retrying won't help
throw new NonRetryableError('User not found');
}
return user;
});
}
}
适用 NonRetryableError 的情况:
- ✅ 身份验证/授权失败
- ✅ 不会改变的无效输入
- ✅ 资源不存在(404)
- ✅ 验证错误
- ❌ 网络故障(应该重试)
- ❌ 速率限制(应该使用退避策略重试)
- ❌ 暂时性服务中断(应该重试)
捕获错误以继续工作流
捕获可选步骤的错误,避免工作流失败:
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
await step.do('process payment', async () => { /* critical */ });
try {
await step.do('send email', async () => { /* optional */ });
} catch (error) {
await step.do('log failure', async () => {
await this.env.DB.prepare('INSERT INTO failed_emails VALUES (?, ?)').bind(event.payload.userId, error.message).run();
});
}
await step.do('update status', async () => { /* continues */ });
}
}
平稳降级:
let result;
try {
result = await step.do('call primary API', async () => await callPrimaryAPI());
} catch {
result = await step.do('call backup API', async () => await callBackupAPI());
}
触发工作流
配置绑定(wrangler.jsonc):
{
"workflows": [{
"name": "my-workflow",
"binding": "MY_WORKFLOW",
"class_name": "MyWorkflow",
"script_name": "workflow-worker" // If workflow in different Worker
}]
}
从 Worker 触发:
const instance = await env.MY_WORKFLOW.create({ params: { userId: '123' } });
return Response.json({ id: instance.id, status: await instance.status() });
实例管理:
const instance = await env.MY_WORKFLOW.get(instanceId);
const status = await instance.status(); // { status: 'running'|'complete'|'errored'|'queued', error, output }
await instance.sendEvent({ type: 'user-action', payload: { action: 'approved' } });
await instance.pause();
await instance.resume();
await instance.terminate();
状态持久化
工作流会自动持久化 step.do() 返回的状态:
✅ 可序列化:
- 基本类型:
string、number、boolean、null - 数组、对象、嵌套结构
❌ 不可序列化:
- Function、Symbol、循环引用、undefined、类实例
示例:
// ✅ Good
const result = await step.do('fetch data', async () => ({
users: [{ id: 1, name: 'Alice' }],
timestamp: Date.now(),
metadata: null
}));
// ❌ Bad - function not serializable
const bad = await step.do('bad', async () => ({ data: [1, 2, 3], transform: (x) => x * 2 })); // Throws error!
跨步骤访问状态:
const userData = await step.do('fetch user', async () => ({ id: 123, email: '[email protected]' }));
const orderData = await step.do('create order', async () => ({ userId: userData.id, orderId: 'ORD-456' }));
await step.do('send email', async () => sendEmail({ to: userData.email, subject: `Order ${orderData.orderId}` }));
可观测性
内置指标(2025 年增强)
工作流会自动跟踪:
- 实例状态:排队、运行中、已完成、出错、已暂停、等待中
- 步骤执行情况:开始/结束时间、耗时、成功/失败
- 重试历史:尝试次数、错误、延迟
- 休眠状态:工作流将于何时唤醒
- 输出:步骤和 run() 的返回值
- CPU 时间(2025 年 4 月 GA):每个实例的活跃处理时间,可用于计费分析
在控制面板中查看指标
通过 Cloudflare 控制面板访问:
- Workers & Pages
- 选择你的工作流
- 查看实例和指标
指标包括:
- 创建的实例总数
- 成功率/错误率
- 平均执行时间
- 步骤级性能
- CPU 时间用量(2025 年功能)
以编程方式访问
const instance = await env.MY_WORKFLOW.get(instanceId);
const status = await instance.status();
console.log(status);
// {
// status: 'complete' | 'running' | 'errored' | 'queued' | 'waiting' | 'unknown',
// error: string | null,
// output: { userId: '123', status: 'processed' }
// }
CPU 时间配置(2025 年):
// wrangler.jsonc
{ "limits": { "cpu_ms": 300000 } } // 5 minutes max (default: 30 seconds)
限制(2025 年更新)
| 功能 | Workers Free | Workers Paid |
|---|---|---|
| 每个工作流的最大步骤数 | 1,024 | 1,024 |
| 每个步骤的最大状态 | 1 MiB | 1 MiB |
| 每个实例的最大状态 | 100 MB | 1 GB |
| 最大事件负载大小 | 1 MiB | 1 MiB |
| 最大 sleep/sleepUntil 时长 | 365 天 | 365 天 |
| 最大 waitForEvent 超时时长 | 365 天 | 365 天 |
| 每个步骤的 CPU 时间 | 10 ms | 30 秒(默认),5 分钟(最大) |
| 每个步骤的时长(墙钟时间) | 无限制 | 无限制 |
| 工作流执行次数上限 | 100,000/天 | 无限制 |
| 并发实例数 | 25 | 10,000(2025 年 10 月,从 4,500 提高) |
| 实例创建速率 | 100/秒 | 100/秒(2025 年 10 月,速度提升 10 倍) |
| 排队实例数上限 | 100,000 | 1,000,000 |
| 每个实例的子请求数上限 | 50/请求 | 1,000/请求 |
| 保留期限(已完成状态) | 3 天 | 30 天 |
| Workflow 名称长度上限 | 64 个字符 | 64 个字符 |
| 实例 ID 长度上限 | 100 个字符 | 100 个字符 |
重要说明:
step.sleep()和step.sleepUntil()不计入 1,024 步骤的上限- 等待中的实例(休眠、重试或等待事件)不计入并发限制
- 实例创建速率提升 10 倍(2025 年 10 月):每 10 秒 100 个 → 每秒 100 个
- 并发数上限提高(2025 年 10 月):4,500 → 10,000 个并发实例
- 状态持久化限制提高(2025 年):每个步骤从 128 KB 提高到 1 MiB,每个实例为 100 MB - 1 GB
- 事件负载大小上限提高(2025 年):128 KB → 1 MiB
- 可通过
wrangler.jsonc配置 CPU 时间:{ "limits": { "cpu_ms": 300000 } }(最长 5 分钟)
定价
需要 Workers Paid 计划(每月 $5)
工作流执行:
- 每月前 10,000,000 次步骤执行:免费
- 超出后:每百万次步骤执行 $0.30
计为一次步骤执行的情况:
- 每次
step.do()调用 - 每次步骤重试
step.sleep()、step.sleepUntil()、step.waitForEvent()不计入
费用示例:
- 包含 5 个步骤且无重试的工作流:5 次步骤执行
- 包含 3 个步骤,其中 1 个步骤重试 2 次的工作流:5 次步骤执行(3 + 2)
- 每月运行 10M 个简单工作流(每个 5 个步骤):((50M - 10M) / 1M) × $0.30 = 每月 $12
Vitest 测试(2025 年 4 月 GA)
工作流支持通过 cloudflare:test 模块进行完整的测试集成。
设置
npm install -D vitest@latest @cloudflare/vitest-pool-workers@latest
vitest.config.ts:
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({ test: { poolOptions: { workers: { miniflare: { bindings: { MY_WORKFLOW: { scriptName: 'workflow' } } } } } } });
自省 API
import { env, introspectWorkflowInstance } from 'cloudflare:test';
it('should complete workflow', async () => {
const instance = await introspectWorkflowInstance(env.MY_WORKFLOW, 'test-123');
try {
await instance.modify(async (m) => {
await m.disableSleeps(); // Skip all sleeps
await m.mockStepResult({ name: 'fetch data' }, { users: [{ id: 1 }] }); // Mock step result
await m.mockEvent({ type: 'approval', payload: { approved: true } }); // Send mock event
await m.mockStepError({ name: 'call API' }, new Error('Network timeout'), 1); // Force error once
});
await env.MY_WORKFLOW.create({ id: 'test-123' });
await expect(instance.waitForStatus('complete')).resolves.not.toThrow();
} finally {
await instance.dispose(); // Cleanup
}
});
测试修饰符
disableSleeps(steps?)- 立即跳过休眠mockStepResult(step, result)- 模拟 step.do() 的结果mockStepError(step, error, times?)- 强制 step.do() 抛出错误mockEvent(event)- 向 step.waitForEvent() 发送模拟事件forceStepTimeout(step, times?)- 强制 step.do() 超时forceEventTimeout(step)- 强制 step.waitForEvent() 超时
官方文档:https://developers.cloudflare.com/workers/testing/vitest-integration/
相关文档
- Cloudflare Workflows 文档:https://developers.cloudflare.com/workflows/
- 入门指南:https://developers.cloudflare.com/workflows/get-started/guide/
- Workers API:https://developers.cloudflare.com/workflows/build/workers-api/
- Vitest 测试:https://developers.cloudflare.com/workers/testing/vitest-integration/
- 休眠与重试:https://developers.cloudflare.com/workflows/build/sleeping-and-retrying/
- 事件与参数:https://developers.cloudflare.com/workflows/build/events-and-parameters/
- 限制:https://developers.cloudflare.com/workflows/reference/limits/
- 定价:https://developers.cloudflare.com/workflows/platform/pricing/
- 更新日志:https://developers.cloudflare.com/workflows/reference/changelog/
- MCP 工具:使用
mcp__cloudflare-docs__search_cloudflare_documentation获取最新文档
最后更新:2026-01-21 版本:2.0.0 变更:新增 12 项已记录的已知问题(TIER 1-2 研究发现):waitForEvent 超时错误、getPlatformProxy 故障、重定向导致实例丢失、Vitest CI 问题、本地开发限制、状态持久化规则、缓存注意事项和幂等模式 维护者:Jeremy Dawes | [email protected]