Steps that a workflow starts together run in parallel. EZGH runs each one as its own invocation, retried on its own, and invokes the workflow again as they finish.
Promise.all, Promise.race and Promise.any over steps (including sleeps, callbacks and child
workflows) hand every step to EZGH at once:
const [crm, billing] = await Promise.all([
step.run("crm", () => crm.upsert(user)),
step.run("billing", () => billing.createCustomer(user)),
])
const sizes = await Promise.all(urls.map((url, i) => step.run(`size-${i}`, () => fetchSize(url))))
const first = await Promise.race([
step.run("work", doWork),
step.sleep("limit", "10m").then(() => "timed out"),
])Go has no implicit concurrency: run branches with Parallel, All or Race. Each branch gets
its own Context and must call steps with it.
var crm, billing string
err := workflows.Parallel(ctx,
func(c workflows.Context) (err error) { crm, err = workflows.Run(c, "crm", upsertCRM); return },
func(c workflows.Context) (err error) { billing, err = workflows.Run(c, "billing", createCustomer); return },
)Parallelreturns once every branch has returned: nil, the one error, or the errors joined in branch order.Allreturns at the first error (the other branches keep running), or nil once every branch has returned, likePromise.all.Racereturns the index and error of the first branch to return. The other branches keep running.
In an async def workflow, asyncio.gather, asyncio.wait and asyncio.as_completed over
steps run them in parallel. A plain def workflow uses ctx.step.parallel:
crm, billing = await asyncio.gather(
ctx.step.run("crm", sync_crm),
ctx.step.run("billing", bill),
)
# In a plain def workflow
a, b = ctx.step.parallel(("part-a", lambda: part(rows, 0)), ("part-b", lambda: part(rows, 1)))Rules
- Steps pending at the same time need distinct names (or explicit IDs), such as
size-${i}. - A run has at most 32 step invocations at once. Further steps wait their turn, in order.
- At most 1,000 steps can start together; more fails the run. Start them in batches, or fan out through child workflows.
- A run has at most 1,000 steps in all, each child workflow counting as one.
- Races are deterministic. A replay resolves recorded steps one at a time in the order they finished, so a race takes the same branch every time.
- Each parallel step uses its own retry policy, and a failed step doesn’t stop its siblings.