Files
cell/docs/requestors.md

177 lines
4.0 KiB
Markdown

---
title: "Requestors"
description: "Asynchronous work with requestors"
weight: 25
type: "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:
```javascript
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
```javascript
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:
```javascript
var constant = function(callback, value) {
callback(42)
}
```
A requestor that always fails:
```javascript
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.
```javascript
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.
```javascript
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.
```javascript
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.
```javascript
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:
```javascript
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:
```javascript
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]})
```