Skip to main content

Parallelism And Async Evaluation

Parallel work within a single FUME mapping can occur when independent branches of the expression wait for I/O operations such as a FHIR server response. In those cases, FUME can start the independent work together and finish when the slowest branch completes.

See also Async And Concurrency for the function index behind this topic.

Three parallelization patterns

FUME supports three patterns for running independent work together:

All three patterns become performance-relevant when the work inside them is async. Regular non-async expressions can still appear inside those branches, but they usually do not become meaningfully faster just because they are wrapped in a parallel shape.

Static parallelism with array syntax

The array constructor is first and foremost the normal way to build arrays in JSONata and FUME. Most uses of [] have nothing to do with concurrency.

Having said that, the array constructor is also the simplest way to start parallel execution of async branches, and can be used when the branches are fixed (known at authoring time). The general array constructor behavior is documented in Other Operators.

[$wait(1000), $wait(1000), $wait(1000)]

The three timers will start together. That means the total duration is one second rather than three. Completion is bounded by the slowest async branch, not by the sum of all branch durations.

This pattern is often enough when you have a fixed set of independent tasks. Use it before reaching for a mapping helper.

Dynamic parallelism with $pMap()

Use $pMap() when the item list is not fixed ahead of time and each item can use the same supplied function.

$pMap() preserves input order in its returned result, waits for all mapping calls to settle, and omits mapped values that are undefined. It is the dynamic equivalent of a static parallel array when you want everything to start together.

Dynamic parallelism with $pLimit()

Use $pLimit() when the item list is dynamic but you do not want every item in flight at once.

$pLimit() still preserves input order, but it starts only up to the requested limit concurrently. This is the safer choice when a remote dependency should not be flooded, or when related items should stay on the same lane through the optional key function.

Async capability vs performance benefit

Not every function in FUME is async. The async badge appears on helpers that actually wait on external work, plus $wait().

That distinction is why parallelization is not automatically a speedup:

  • Wrapping purely in-memory work such as $sum([1..100000]) in a parallel shape does not create a meaningful performance gain by itself.
  • Wrapping a call such as $count($search("Patient", {"identifier": identifier}, {"fetchAll": true})) can matter, because $search() is async even though $count() is not.

Regular non-async functions can still appear inside a parallelized branch. They just are not usually the reason the branch becomes faster.

FHIR crawl example

FHIR search is a good example of when parallelism is worth using because the expensive part is usually waiting on remote responses.

For a fixed resource type list, the array constructor is the clearest pattern:

[
$search("Practitioner", {}, {"fetchAll": true}),
$search("PractitionerRole", {}, {"fetchAll": true}),
$search("Organization", {}, {"fetchAll": true})
]

Those independent fetchAll searches will be performed in parallel. The full expression finishes when the slowest resource-type crawl completes.

If the list is dynamic, use $pMap() or $pLimit():

$pMap(resourceTypes, function($resourceType) {
$search($resourceType, {}, {"fetchAll": true})
})
$pLimit(resourceTypes, 2, function($resourceType) {
$search($resourceType, {}, {"fetchAll": true})
})

Use the unconstrained form when you want the best latency and the remote system can tolerate it. Use the limited form when the crawl should stay bounded.

Relevant async FHIR helpers include $search(), $searchSingle(), $resolve(), $literal(), $resourceId(), and $capabilities().

Ordering and completion semantics

Parallel execution does not change the documented output ordering:

  • The array constructor returns results in array order.
  • $pMap() returns results in the original input order.
  • $pLimit() also returns results in the original input order, even though work may be spread across concurrent lanes.

In all three patterns, overall completion waits for the slowest outstanding async branch. Parallel scheduling does not imply that every kind of work becomes faster; it means independent async work can overlap.

For a browsable list of the helpers involved in this model, see Async And Concurrency.

FLASH parsing

If a parallelized entry contains FLASH blocks, that entry's parsing work can also benefit from parallelism, since FLASH parsing may involve reading FHIR definitions from disk.

This matters mostly on the first parse of a specific expression string, since previously parsed expressions are cached and reused.