Skip to main content

resonate-async-await-engine-typescript

The Resonate TypeScript SDK's async/await execution engine — import from @resonatehq/sdk/async, register async functions, eager ctx.run fan-out with Promise.all, Never-default retries and Exponential opt-in, and when to choose the async engine vs the generator engine. Introduced in v0.11.0. Use when writing new TypeScript workflows with async/await instead of function*/yield*.

跳到安装

来源信息

仓库
resonatehq/resonate-skills
最近来源活动
2026年8月5日 23:29
检测到的 SKILL.md 语言
英语
星标
6
分支
0

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
resonate-async-await-engine-typescript
description
The Resonate TypeScript SDK's async/await execution engine — import from @resonatehq/sdk/async, register async functions, eager ctx.run fan-out with Promise.all, Never-default retries and Exponential opt-in, and when to choose the async engine vs the generator engine. Introduced in v0.11.0. Use when writing new TypeScript workflows with async/await instead of function*/yield*.
license
Apache-2.0
# Resonate Async/Await Engine (TypeScript) > **SDK version:** This skill reflects `@resonatehq/sdk` v0.11.4 (current on npm). The async/await engine was introduced in v0.11.0. ## Overview The TypeScript SDK ships two execution engines: | Engine | Import path | Function style | Release | |---|---|---|---| | **Generator engine** | `@resonatehq/sdk` | `function*` / `yield*` | v0.10.x and earlier; still current | | **Async/await engine** | `@resonatehq/sdk/async` | `async function` / `await` | v0.11.0+ | Both engines connect to the same Resonate Server, share the same durable-promise substrate, and support the same patterns (fan-out, human-in-the-loop, saga, etc.). The async engine is opt-in — existing generator-engine code is unaffected. For the generator engine, see `resonate-basic-ephemeral-world-usage-typescript` and `resonate-basic-durable-world-usage-typescript`. --- ## When to use the async engine - Writing new TypeScript workflows and prefer `async/await` over `function*`/`yield*` - Integrating Resonate into a codebase that already uses `async/await` throughout - Browser or edge environments: `@resonatehq/sdk/async` exports are browser-compatible ## When to keep the generator engine - Existing generator-engine code — there is no reason to migrate; the generator engine is fully supported - You need `ctx.detached()` with the full bounded-replay pattern (async engine also has `detach()` but check the SDK changelog for your version) --- ## Installation ```bash npm install @resonatehq/sdk@0.11.4 ``` Both engines are in the same package. The generator engine is the default export; the async engine is the `/async` sub-export. --- ## Basic usage ```typescript import { Resonate } from "@resonatehq/sdk/async"; const resonate = new Resonate({ url: "http://localhost:8001" }); // Register an async function resonate.register("greet", async (ctx, name: string) => { return `Hello, ${name}!`; }); // Start a workflow — resonate.run(id, funcOrName, ...args) → Promise<ResonateHandle<T>> const handle = await resonate.run("greet-001", "greet", "world"); // Await the result via the durable-promise subscription const result = await handle.result(); // result === "Hello, world!" ``` **Key differences from the generator engine:** - Import is `from "@resonatehq/sdk/async"` not `from "@resonatehq/sdk"` - Functions are `async function` not `function*` - `ctx.run(func, ...args)` in the async engine has **no id argument** (ID is auto-generated); in the generator engine `ctx.run(fn, ...args)` is also ID-less at the context level, but the API shape differs - No `beginRun` / `beginRpc` on the async-engine client — every `run` and `rpc` already returns a `ResonateHandle<T>` directly, so `beginRun` would be redundant --- ## Eager ctx.run and fan-out with Promise.all Inside an async workflow function, `ctx.run(func, ...args)` returns a `DurablePromise<T>` immediately — the child starts executing right away. Hold several and await them together for parallel fan-out: ```typescript resonate.register("processItems", async (ctx, items: string[]) => { // Start all children eagerly — they run in parallel const promises = items.map((item) => ctx.run(processOne, item) ); // Await all results const results = await Promise.all(promises); return results; }); resonate.register("processOne", async (ctx, item: string) => { // ... durable leaf work return `processed: ${item}`; }); ``` This is the async-engine equivalent of the generator engine's `ctx.beginRun` + sequential `yield*` pattern. --- ## Retries: Never default, Exponential opt-in The async/await engine defaults to **no retry** (`Never`) when no retry policy is specified. To opt into retries on a specific `ctx.run` call, pass `ctx.options({ retryPolicy: ... })`: ```typescript import { Resonate, Exponential, Never } from "@resonatehq/sdk/async"; resonate.register("reliableStep", async (ctx, taskId: string) => { // Default: Never retry — a failure propagates immediately const noRetryResult = await ctx.run(fragileOp, taskId); // Explicit Exponential retry on a specific child const retryResult = await ctx.run( flakeyOp, taskId, ctx.options({ retryPolicy: new Exponential() }), ); return { noRetryResult, retryResult }; }); ``` `Never`, `Exponential`, `Linear`, and `Constant` are all exported from `@resonatehq/sdk/async` and can be used with both engines. **Tip:** For saga-style compensation, let steps fail immediately with the default `Never` policy and catch in the outer function. --- ## ctx.options() in the async engine `ctx.options()` accepts the same fields as the generator engine: ```typescript ctx.options({ retryPolicy: new Exponential(), // retry policy (default: Never) target: "poll://any@workers", // worker group routing timeout: 30_000, // ms; defaults to 24h tags: { "env": "prod" }, // arbitrary tags on the child promise version: 1, // function version pin }) ``` --- ## Migration from generator engine | Generator engine | Async/await engine | |---|---| | `import { Resonate } from "@resonatehq/sdk"` | `import { Resonate } from "@resonatehq/sdk/async"` | | `function* workflow(ctx: Context, ...)` | `async function workflow(ctx: Context, ...)` | | `yield* ctx.run(fn, args)` | `await ctx.run(fn, args)` | | `yield* ctx.rpc(fn, args)` | `await ctx.rpc(fn, args)` | | `yield* ctx.sleep(ms)` | `await ctx.sleep(ms)` | | `const p = yield* ctx.promise(opts)` | `const dp = ctx.promise(opts)` — no await; returns `DurablePromise` with `.id` | | `const val = yield* p` | `const val = await dp` | | `resonate.beginRun(id, fn, ...args)` | `resonate.run(id, fn, ...args)` (returns handle) | | `await resonate.run(id, fn, ...args)` | `(await resonate.run(id, fn, ...args)).result()` | The server-side promise structure is identical; existing generator-engine workflows on the server are unaffected by the async engine. See the [SDK README migration guide](https://github.com/resonatehq/resonate-sdk-ts#migrating-from-generators) for the authoritative changelog. --- ## External promise resolution (human-in-the-loop) The async engine uses the same `resonate.promises.resolve()` API as the generator engine: ```typescript // Inside a workflow: park on an external decision resonate.register("awaitApproval", async (ctx, orderId: string) => { const dp = ctx.promise<{ approved: boolean }>(); // dp.id is available synchronously — surface it to whoever needs to approve console.log("Approval promise id:", dp.id); const decision = await dp; return decision; }); // In a webhook handler: resolve the parked promise const data = Buffer.from(JSON.stringify({ approved: true })).toString("base64"); await resonate.promises.resolve(promiseId, { data }); ``` --- ## Full runnable example ```typescript import { Resonate, Exponential } from "@resonatehq/sdk/async"; const resonate = new Resonate({ url: "http://localhost:8001" }); resonate.register("summarize", async (ctx, texts: string[]) => { // Fan-out: summarize each text in parallel const summaries = await Promise.all( texts.map((t) => ctx.run( summarizeOne, t, ctx.options({ retryPolicy: new Exponential() }), ) ) ); return summaries.join("\n"); }); resonate.register("summarizeOne", async (ctx, text: string) => { // ... call an LLM or summarization service return `Summary of: ${text.slice(0, 50)}`; }); const handle = await resonate.run("summarize-job-001", "summarize", [ "First document ...", "Second document ...", ]); console.log(await handle.result()); await resonate.stop(); ```
在 GitHub 查看