Skip to content

Continuous Mode

By default, tasks run in linear mode — each node processes a single message and passes its result to the next node. Continuous mode changes this: service nodes and logic nodes can yield multiple messages over time, and the task stays alive to process each one.

Continuous mode is designed for workflows that react to a stream of events rather than a single input:

  • An email listener that emits a message for every new email
  • A file watcher that emits when files appear in a directory
  • A webhook receiver that forwards incoming payloads
  • A data pipeline that processes records one by one from a large source
  • A logic node that fans out work by yielding multiple intermediate results

In continuous mode, nodes use yield to emit values instead of return. Each yield pushes a new message through the downstream nodes in the task template.

Service nodes:

createService('1.0.0', async function* (ctx) {
for await (const email of watchInbox(ctx.configuration.folder)) {
yield { subject: email.subject, from: email.from }
}
})

Logic nodes can also yield multiple times. This is useful for fanning out data or generating intermediate steps:

// Logic node: emit one message per item in the input array
for (const item of context.input.items) {
yield { processed: transform(item) }
}

2. Set the Task Template to Continuous Mode

Section titled “2. Set the Task Template to Continuous Mode”

In the task template settings, set the mode to Continuous. This tells Requence that nodes are allowed to emit multiple messages and the task should not exit after the first result.

When a task is stopped — via the UI, the API, or an exit node — Requence signals the running generator to stop. If your generator is suspended in a long await (for example, sleeping until the next scheduled tick), it will not exit until that await resolves naturally.

To exit immediately, pass ctx.terminated.signal to any abort-aware API:

import { setTimeout } from 'node:timers/promises'
// A cron-style emitter: fires on schedule, exits immediately when stopped
createService('1.0.0', async function* (ctx) {
while (true) {
const delay = getNextTickDelay(ctx.configuration.expression)
await setTimeout(delay, null, { signal: ctx.terminated.signal })
yield { firedAt: new Date().toISOString() }
}
})

ctx.terminated.signal is a native AbortSignal that fires the moment the task is stopped. It works with any API that accepts a signal: fetch, setTimeout, database clients, and so on. The framework automatically handles the resulting AbortError — no try/catch is needed in your generator.

When none of your awaits support AbortSignal, you can race ctx.terminated as a plain Promise instead:

const result = await Promise.race([
waitForExternalEvent(),
ctx.terminated,
])

See Context API — ctx.terminated for the full reference.

If a continuous service disconnects while running, Requence automatically detects the disconnection and re-dispatches the original message so a new service instance can pick up the work. This happens transparently — the task continues without manual intervention.

Since nodes can emit multiple values, you often need a downstream logic node to aggregate or filter them. A common pattern is to collect results and exit when a condition is met:

// Logic node: collect yielded values and exit after 10
context.state.items ??= []
context.state.items.push(context.getNodeData('email-listener'))
if (context.state.items.length >= 10) {
return context.state.items
}

Because this logic node runs once per upstream yield, set its concurrency to off to prevent race conditions on the shared context.state.

Since nodes stay alive after yielding a message, continuous mode enables bidirectional communication via callbacks. A service can yield a callback object as part of its output, pause execution, and wait for a downstream node to invoke it with a response — then continue based on that value.

In continuous mode, the Max node executions setting acts as a per-second rate limit rather than a total count. If more than the configured number of node executions occur within a single second, the task is aborted. This protects against infinite loops (e.g. a logic node feeding back into itself) while allowing long-running tasks to process data indefinitely at a normal pace.

The default value is 10 executions per second. Increase it for high-throughput workflows.

  • Generator handlers for services — continuous services require the handler to be a generator function. Regular function handlers always produce a single result.
  • No exit required — continuous task templates may not have exit nodes if they are designed to run indefinitely. They can be stopped via the API or the Requence UI.
  • Stateless recovery — when a service is recovered after a crash, it receives the original input and configuration. If the service needs to resume from where it left off, it is the developer’s responsibility to manage that state externally.