Files
cell/docs/requestors.md

4.0 KiB

title, description, weight, type
title description weight type
Requestors Asynchronous work with requestors 25 docs

Requestors are functions that encapsulate asynchronous work. They provide a structured way to compose callbacks, manage cancellation, and coordinate concurrent operations between actors.

What is a Requestor

A requestor is a function with this signature:

var my_requestor = function(callback, value) {
  // Do async work, then call callback with result
  // Return a cancel function
}
  • callback — called when the work completes: callback(value, reason)
    • On success: callback(result) or callback(result, null)
    • On failure: callback(null, reason) where reason explains the failure
  • value — input passed from the previous step (or the initial caller)
  • return — a cancel function, or null if cancellation is not supported

The cancel function, when called, should abort the in-progress work.

Writing a Requestor

var fetch_data = function(callback, url) {
  $contact(function(connection) {
    send(connection, {get: url}, function(response) {
      callback(response)
    })
  }, {host: url, port: 80})
  return function() {
    // clean up if needed
  }
}

A requestor that always succeeds immediately:

var constant = function(callback, value) {
  callback(42)
}

A requestor that always fails:

var broken = function(callback, value) {
  callback(null, "something went wrong")
}

Composing Requestors

ƿit provides four built-in functions for composing requestors into pipelines.

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) {
    print(reason)
  } else {
    print(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) {
    print("all sources failed")
  }
}, key)

Timeouts

Wrap any requestor with $time_limit to add a timeout:

var timed = $time_limit(fetch_data, 5)  // 5 second timeout

timed(function(result, reason) {
  // reason will explain timeout if it fires
}, url)

If the requestor does not complete within the time limit, it is cancelled and the callback receives a failure.

Requestors and Actors

Requestors are particularly useful with actor messaging. Since send is callback-based, it fits naturally:

var ask_worker = function(callback, task) {
  send(worker, task, function(reply) {
    callback(reply)
  })
}

var pipeline = sequence([
  ask_worker,
  process_result,
  store_result
])

pipeline(function(stored) {
  print("done")
  $stop()
}, {type: "compute", data: [1, 2, 3]})