Parallelism
ƿit provides five ways to compose requestors. Four of them — sequence, parallel, race, and fallback — take an array of requestors and return a new requestor, so pipelines nest and compose freely. The fifth, $time_limit, wraps a single requestor with a deadline.
The four composers are engine values: an executable that uses them claims them, and the runtime binds them when the actor starts. $time_limit is an endowment, because granting a deadline is granting access to time.
If programs are designed around requestors, the same code works whether the work is local or on another machine: a requestor that calls a local function and one that sends a message to a remote actor have the same shape.
sequence(requestor_array)
Run requestors one after another. Each result becomes the input to the next. The final result is passed to the callback.
var pipeline = sequence([
fetch_user,
validate_permissions,
load_profile
])
pipeline(function(profile, reason) {
if (reason) {
log.error(reason)
} else {
log.console(profile.name)
}
}, user_id)
If any step fails, the remaining steps are skipped and the failure propagates.
parallel(requestor_array, throttle, need)
Start all requestors concurrently. Results are collected into an array matching the input order.
var both = parallel([
fetch_profile,
fetch_settings
])
both(function(results, reason) {
var profile = results[0]
var settings = results[1]
}, user_id)
- throttle — limit how many requestors run at once (null for no limit)
- need — minimum number of successes required (default: all)
race(requestor_array, throttle, need)
Like parallel, but returns as soon as the needed number of results arrive. Unfinished requestors are cancelled.
var fastest = race([
fetch_from_cache,
fetch_from_network,
fetch_from_backup
])
fastest(function(results) {
// results[0] is whichever responded first
}, request)
Default need is 1. Useful for redundant operations where only one result matters.
fallback(requestor_array)
Try each requestor in order. If one fails, try the next. Return the first success.
var resilient = fallback([
fetch_from_primary,
fetch_from_secondary,
use_cached_value
])
resilient(function(data, reason) {
if (reason) {
log.error("all sources failed")
}
}, key)
$time_limit(requestor, seconds)
Wrap any requestor with a deadline:
var timed = $time_limit(fetch_data, 5) // 5 second timeout
timed(function(result, reason) {
// reason explains the timeout if it fires
}, url)
If the requestor does not complete within the time limit, it is cancelled and the callback receives (null, reason) exactly once. Composes like any other requestor — race([$time_limit(a, 5), b]).
Location Transparency
Because requestors have a uniform shape, the code that composes them doesn’t need to know where the work happens. A requestor that operates on a closed-over local object and one that sends messages to a remote actor are interchangeable:
// local requestor — works on a closed-over cache
var local_lookup = function(callback, key) {
callback(cache[key])
}
// remote requestor — asks another actor
var remote_lookup = function(callback, key) {
send(db_actor, {get: key}, function(reply) {
callback(reply.value)
})
}
// either one works here
var pipeline = sequence([
local_lookup, // swap for remote_lookup without changing anything else
process_result
])