Skip to content

Control Task

Not all features are equally important. An analytics tracker failing is acceptable — a missing user session is not. When a critical feature fails, the app shouldn’t silently show a broken page; it should acknowledge the problem.

A control task is a pattern for this. It observes the statuses of critical tasks and returns a result — { passed: true } or { passed: false } — that other parts of your app can use to decide what to do.

Using task.status in context means the control task always runs — even when upstream tasks have failed or were skipped — which is what makes it a reliable observer.

const control = createTask({
name: "control",
run: {
context: [fetchUser.status],
fn: (ctx) => {
const passed = ctx.every((status) => status === "done")
return { ok: { passed } }
},
},
})
compose()
.stage({ steps: [fetchUser, analytics] })
.stage({ steps: [control] })
.run()
.then((scope) => scope.get(control.result))
.then(console.log)

Try uncommenting the failure in fetchUser to see the control task catch it:

import { compose, createTask } from "@grlt-hub/app-compose"
// Critical — the page can't work without this
const fetchUser = createTask({
name: "fetchUser",
run: {
fn: () => {
// 👇 Uncomment to simulate failure
// throw new Error("[fetchUser]: failed")
return { ok: { id: 1, name: "Alice" } }
},
},
})
// Non-critical — nice to have, but the page works without it
const analytics = createTask({
name: "analytics",
run: {
fn: () => {
throw new Error("service unavailable")
},
},
})
// Control task:
// observes critical tasks and decides if the app start succeeded
const control = createTask({
name: "control",
run: {
context: [fetchUser.status],
fn: (ctx) => {
const passed = ctx.every((status) => status === "done")
return { ok: { passed } }
},
},
})
compose()
.stage({ steps: [fetchUser, analytics] })
.stage({ steps: [control] })
.run()
.then((scope) => scope.get(control.result))
.then(console.log)