Skip to content

Conditional Execution

Execute steps only when certain conditions are met, with automatic event emission for skipped steps.

import { ok, err, type AsyncResult } from 'awaitly';
import { when, unless, createWorkflow } from 'awaitly/workflow';
const fetchUser = async (id: string): AsyncResult<User, 'NOT_FOUND'> => {
// ...
};
const sendEmail = async (to: string): AsyncResult<void, 'SEND_FAILED'> => {
// ...
};
const workflow = createWorkflow('workflow', { fetchUser, sendEmail });
const result = await workflow.run(async ({ step, deps }) => {
const user = await step('fetchUser', () => fetchUser('123'));
// Only send email if user is not verified
await when(
!user.isVerified,
() => step('sendEmail', () => sendEmail(user.email)),
{ name: 'send-verification', reason: 'User is already verified' }
);
return user;
});

Run a step only when a condition is true. Returns undefined if skipped.

const result = await workflow.run(async ({ step, deps }) => {
const user = await step('fetchUser', () => fetchUser('123'));
// Only fetch premium data if user is premium
const premium = await when(
user.isPremium,
() => step('fetchPremiumData', () => fetchPremiumData(user.id)),
{ name: 'premium-data', reason: 'User is not premium' }
);
return { user, premium }; // premium is User | undefined
});

Run a step only when a condition is false. Returns undefined if skipped.

const result = await workflow.run(async ({ step, deps }) => {
const user = await step('fetchUser', () => fetchUser('123'));
// Only send verification email if user is NOT verified
const email = await unless(
user.isVerified,
() => step('sendVerificationEmail', () => sendVerificationEmail(user.email)),
{ name: 'send-verification', reason: 'User is already verified' }
);
return { user, email }; // email is void | undefined
});

whenOr - Execute if true, else return default

Section titled “whenOr - Execute if true, else return default”

Run a step if condition is true, otherwise return a default value.

const result = await workflow.run(async ({ step, deps }) => {
const user = await step('fetchUser', () => fetchUser('123'));
// Get premium limits or use default for non-premium users
const limits = await whenOr(
user.isPremium,
() => step('fetchPremiumLimits', () => fetchPremiumLimits(user.id)),
{ maxRequests: 100, maxStorage: 1000 }, // default
{ name: 'premium-limits', reason: 'Using default limits' }
);
return { user, limits }; // limits is PremiumLimits | DefaultLimits
});

unlessOr - Execute if false, else return default

Section titled “unlessOr - Execute if false, else return default”

Run a step if condition is false, otherwise return a default value.

const result = await workflow.run(async ({ step, deps }) => {
const user = await step('fetchUser', () => fetchUser('123'));
// Generate new token if NOT authenticated, else use existing
const token = await unlessOr(
user.isAuthenticated,
() => step('generateNewToken', () => generateNewToken(user.id)),
user.existingToken, // default
{ name: 'token-generation', reason: 'Using existing token' }
);
return { user, token };
});

Conditional helpers automatically emit step_skipped events when steps are skipped:

const workflow = createWorkflow('workflow', { fetchUser }, {
onEvent: (event) => {
if (event.type === 'step_skipped') {
console.log(`Step ${event.name} skipped: ${event.reason}`);
// event.decisionId - unique ID for this decision
}
}
});

Use createConditionalHelpers to bind helpers to workflow context for automatic event emission:

import { createConditionalHelpers, createWorkflow } from 'awaitly/workflow';
const workflow = createWorkflow('workflow', { fetchUser }, {
onEvent: (event, ctx) => {
// ctx is available here
}
});
const result = await workflow.run(async ({ step, deps, args, ctx }) => {
// Create bound helpers
const { when, whenOr } = createConditionalHelpers({
workflowId: ctx.workflowId,
onEvent: (e) => {
// Events automatically include context
},
context: ctx
});
const user = await step('fetchUser', () => fetchUser('123'));
// Helpers automatically emit events with context
const premium = await when(
user.isPremium,
() => step('fetchPremiumData', () => fetchPremiumData(user.id)),
{ name: 'premium-data' }
);
return { user, premium };
});

When using run(), pass context manually:

import { run } from 'awaitly/run';
import { createConditionalHelpers } from 'awaitly/workflow';
const result = await run(async ({ step }) => {
const ctx = {
workflowId: 'workflow-123',
onEvent: (event) => {
// Handle events
},
context: { requestId: 'req-456' }
};
const { when } = createConditionalHelpers(ctx);
const user = await step('fetchUser', () => fetchUser('123'));
const premium = await when(
user.isPremium,
() => step('fetchPremiumData', () => fetchPremiumData(user.id)),
{ name: 'premium-data' }
);
return { user, premium };
}, {
onEvent: ctx.onEvent,
workflowId: ctx.workflowId,
context: ctx.context
});

All conditional helpers accept an optional ConditionalOptions object:

{
name?: string; // Human-readable name for the step
key?: string; // Stable identity key for caching/tracking
reason?: string; // Explanation for why step was skipped
}

Skipped steps appear in workflow visualizations:

import { createVisualizer } from 'awaitly-visualizer';
const viz = createVisualizer();
const workflow = createWorkflow('workflow', deps, { onEvent: viz.handleEvent });
await workflow.run(async ({ step, deps }) => {
const user = await step('fetchUser', () => fetchUser('123'));
await when(
user.isPremium,
() => step('fetchPremiumData', () => fetchPremiumData(user.id)),
{ name: 'premium-data', reason: 'Not premium' }
);
return user;
});
console.log(viz.render());
// Shows skipped step with reason
const processOrder = createWorkflow('workflow', { fetchOrder, chargeCard, sendEmail, applyDiscount });
const result = await processOrder.run(async ({ step, deps }) => {
const order = await step('fetchOrder', () => deps.fetchOrder(orderId));
// Apply discount only if order is large enough
const discount = await whenOr(
order.total > 100,
() => step('applyDiscount', () => deps.applyDiscount(order.id, 'BULK_10')),
0, // no discount
{ name: 'apply-discount', reason: 'Order too small for discount' }
);
// Charge card
const payment = await step('chargeCard', () => deps.chargeCard(order.total - discount));
// Send confirmation email only if payment succeeded
await when(
payment.status === 'succeeded',
() => step('sendEmail', () => deps.sendEmail(order.email, 'Order confirmed')),
{ name: 'send-confirmation', reason: 'Payment failed' }
);
return { order, payment, discount };
});

Learn about Testing →