| title | Run work concurrently |
|---|---|
| permalink | /how-to/async |
| diataxis | how-to |
Use async when two defs or named scripts do not depend on each other and you want them to overlap. The runtime starts each call immediately and gives you a handle. The handle becomes a string on the first read that needs the value, or at the end of the current step list.
This page is a recipe. The value model lives in Async Handles. The syntax table lives in Language, async.
- An entry file with
export def main. - Two independent callees (defs or named scripts). Inline
'…'()cannot beasync— move the body into a namedscript.
Hold each handle in its original binding. Any read that needs the string resolves the handle, so avoid these until you want the value:
- interpolating it, such as
log "${lint_h}"; - passing it as a call argument, since a bare argument is rewritten to
${lint_h}before the call; - using it as an
iformatchsubject; - copying it with
const copy = lint_h, since a bare copy is rewritten to"${lint_h}"and resolves too.
An early read makes the def wait at that point, which removes the overlap.
def lint() {
return check_lint()
}
def unit_tests() {
return check_tests()
}
export def main() {
const lint_h = async lint()
const test_h = async unit_tests()
log "lint: ${lint_h}"
log "tests: ${test_h}"
}
A bare async lint() with no capture still starts the work. The implicit join at the end of the step list waits for it.
catch and recover attach only to the statement form. A captured const h = async foo() cannot carry those blocks. Wrap the target in a def if you need both a handle and a retry loop.
export def main() {
async deploy() recover (err) {
logerr "repair; see ${err}"
auto_repair()
}
}
recover retries inside that one branch. catch runs once; a successful catch counts the branch as joined-ok. A catch return becomes the parent def's return when the join adopts it.
for line in h does not resolve a handle. The loop iterates the token as one line, so you get one pass over __JAIPH_HANDLE__… instead of one pass per result line. Resolve first:
const text = "${h}"
for line in text {
log line
}
- Run a file that starts two
asynccalls and reads the handles only at the end. The live tree prefixes each branch with a subscript (₁,₂). - Confirm an unread handle still finishes: drop the
loglines and the run still exits0after both branches complete. - Confirm
for line in hruns once (the token is one line). Afterconst text = "${h}",for line in textruns once per result line.
examples/async.jh is a two-backend sample of the same pattern.
- Async Handles — eager start, lazy resolve, implicit join, and why there is no
await. - Language,
async— surface syntax. - Inbox — channel drain runs after the entry def's implicit join.