惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
博客园 - 三生石上(FineUI控件)
S
Securelist
U
Unit 42
The Cloudflare Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Simon Willison's Weblog
Simon Willison's Weblog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
B
Blog
T
Tenable Blog
The Hacker News
The Hacker News
The Register - Security
The Register - Security
IT之家
IT之家
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
P
Privacy & Cybersecurity Law Blog
博客园_首页
T
Tailwind CSS Blog
人人都是产品经理
人人都是产品经理
C
Cybersecurity and Infrastructure Security Agency CISA
Know Your Adversary
Know Your Adversary
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
T
Tor Project blog
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
A
Arctic Wolf
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
V2EX
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
V
Visual Studio Blog
月光博客
月光博客
爱范儿
爱范儿
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
G
GRAHAM CLULEY
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Heimdal Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

Workflow SDK Documentation

Patterns for Defining Tools Human-in-the-Loop Building Durable AI Agents Queueing User Messages Resumable Streams Sleep, Suspense, and Scheduling Streaming Updates from Tools API Reference Workflow Globals Changelog Resilient run start Cookbook Building a World Deploying Astro Express Fastify Hono Getting Started NestJS Next.js Nitro Nuxt Python SvelteKit Vite corrupted-event-log fetch-in-workflow hook-conflict Errors node-js-module-in-workflow serialization-failed start-invalid-workflow-function step-not-registered timeout-in-workflow webhook-invalid-respond-with-value webhook-response-not-sent workflow-not-registered Errors & Retrying Hooks & Webhooks Idempotency Foundations Serialization Starting Workflows Streaming Versioning Workflows and Steps How the Directives Work Encryption Event Sourcing Framework Integrations Understanding Directives Migration Guides Migrating from AWS Step Functions Migrating from Inngest Migrating from Temporal Observability Testing Server-Based Testing createHook createWebhook defineHook FatalError fetch getStepMetadata getWorkflowMetadata getWritable workflow RetryableError sleep @workflow/vitest DurableAgent @workflow/ai WorkflowChatTransport getHookByToken getRun getWorld workflow/api resumeHook resumeWebhook Chat Session Modeling runtime-decryption-failed Upgrading Workflows abort-signal-timeout-in-workflow Cancellation How Cancellation Works Internal Serializable AbortController and AbortSignal Eager Processing of Steps & Incremental Event Replay TanStack Start Agent Cancellation Workflow Composition Local World | Workflow SDK Postgres World | Workflow SDK Vercel World | Workflow SDK Migrating from trigger.dev Secure Credential Handling Local World | Workflow SDK Postgres World | Workflow SDK Vercel World | Workflow SDK
Sequential & Parallel Execution
2026-04-29 · via Workflow SDK Documentation

Compose steps with familiar async/await patterns — sequential await, Promise.all, and Promise.race.

Workflows are written in plain async/await — there's no new control-flow API to learn. Sequential awaits chain steps that depend on each other, Promise.all runs independent steps in parallel, and Promise.race returns whichever finishes first. These compose with workflow primitives like sleep() and createWebhook() since those are also just promises.

  • Pipelines — each step depends on the previous step's output (validate → process → store)
  • Independent fan-out — fetch multiple resources or perform multiple actions that don't depend on each other
  • Race conditions — return as soon as one of N operations completes (timeout, first-responder, deadline)
  • Mixing primitives — running steps, sleeps, and webhooks side-by-side in the same control-flow expression

Sequential

The simplest way to orchestrate steps is to execute them one after another, where each step depends on the previous step's output.

declare function validateData(data: unknown): Promise<string>; // @setup
declare function processData(data: string): Promise<string>; // @setup
declare function storeData(data: string): Promise<string>; // @setup

export async function dataPipelineWorkflow(data: unknown) {
  "use workflow";

  const validated = await validateData(data);
  const processed = await processData(validated);
  const stored = await storeData(processed);

  return stored;
}

Parallel with Promise.all

When steps don't depend on each other, run them concurrently with Promise.all. The workflow waits until all of them resolve.

declare function fetchUser(userId: string): Promise<{ name: string }>; // @setup
declare function fetchOrders(userId: string): Promise<{ items: string[] }>; // @setup
declare function fetchPreferences(userId: string): Promise<{ theme: string }>; // @setup

export async function fetchUserData(userId: string) {
  "use workflow";

  const [user, orders, preferences] = await Promise.all([ 
    fetchUser(userId), 
    fetchOrders(userId), 
    fetchPreferences(userId), 
  ]); 

  return { user, orders, preferences };
}

Race with Promise.race

Promise.race resolves as soon as the first promise settles. Since sleep() and createWebhook() return promises, they compose naturally — for example, waiting for a webhook callback with a deadline:

import { sleep, createWebhook } from "workflow";

declare function executeExternalTask(webhookUrl: string): Promise<void>; // @setup

export async function runExternalTask(userId: string) {
  "use workflow";

  const webhook = createWebhook();
  await executeExternalTask(webhook.url);

  await Promise.race([ 
    webhook, 
    sleep("1 day"), 
  ]); 

  console.log("Done");
}

For racing operations against deadlines specifically (timeouts), see the dedicated Timeouts recipe — it covers result discrimination, FatalError semantics, and the "loser keeps running" caveat.

Combining sequential, parallel, and durable primitives

Most real workflows combine all three. Here's a simplified version of the birthday card generator demo — sequential card generation, parallel RSVP fan-out, non-blocking webhook collection, and a durable sleep until the birthday:

import { createWebhook, sleep, type Webhook } from "workflow";

declare function makeCardText(prompt: string): Promise<string>; // @setup
declare function makeCardImage(text: string): Promise<string>; // @setup
declare function sendRSVPEmail(friend: string, webhook: Webhook): Promise<void>; // @setup
declare function sendBirthdayCard(text: string, image: string, rsvps: unknown[], email: string): Promise<void>; // @setup

export async function birthdayWorkflow(
  prompt: string,
  email: string,
  friends: string[],
  birthday: Date
) {
  "use workflow";

  const text = await makeCardText(prompt); 
  const image = await makeCardImage(text); 

  const webhooks = friends.map(() => createWebhook());

  await Promise.all( 
    friends.map((friend, i) => sendRSVPEmail(friend, webhooks[i])) 
  ); 

  const rsvps: unknown[] = [];
  webhooks.map((webhook) =>
    webhook.then((req) => req.json()).then(({ rsvp }) => rsvps.push(rsvp))
  );

  await sleep(birthday); 

  await sendBirthdayCard(text, image, rsvps, email);

  return { text, image, status: "Sent" };
}
  1. await is durable. When the workflow awaits a step, the runtime persists the step's input, suspends the workflow, runs the step, and replays the workflow with the step's result on resume. The same applies to sleep() and createWebhook().
  2. Promise.all runs steps concurrently. Each promise in the array is suspended on its own and the workflow resumes only when all have settled. Failures propagate — if any promise rejects, the whole Promise.all rejects.
  3. Promise.race resolves on the first settle. The losing promises keep running in the background but their results are discarded by the workflow.
  4. All primitives are promises. sleep("1 day") and createWebhook() return promises, so they compose with Promise.all / Promise.race exactly like steps do — this is what makes patterns like "race a webhook against a 24-hour deadline" a one-liner.
  • Replace Promise.all with Promise.allSettled when partial failures should not abort the rest. You'll get an array of { status, value | reason } instead of throwing on the first rejection.
  • Bound the parallelismPromise.all over 1000 items will fan out 1000 concurrent steps. If your downstream APIs can't handle that, batch the array into chunks (see Batching).
  • Add a deadline to any race — pair the operation with sleep("30s").then(() => "timeout" as const) and check the discriminated result. See Timeouts.
  • Mix steps and hooks in a race — wait for an external signal or a deadline or a step result, all in the same Promise.race. The first one to resolve wins.