@endo/eventual-sendEventual send: a uniform async messaging API for local and remote objects.
The @endo/eventual-send package provides the E() proxy for asynchronous
message passing.
Whether an object is in the same vat, a different vat, or across a network,
E() provides a consistent API that always returns promises.
This enables:
Eventual send relies on an Endo environment.
Programs running in an existing Endo platform like an Agoric smart contract or
an Endo plugin do not need to do anything special to set up HardenedJS,
HandledPromise and related shims.
To construct an environment suitable for Eventual Send requires the
HandledPromise shim:
import '@agoric/eventual-send/shim.js';
The shim ensures that every instance of Eventual Send can recognize every other instance's handled promises. This is how we mitigate, what we call, "eval twins".
import { E } from '@endo/eventual-send';
Eventual send: invoke a method, returning a promise for the result.
import { E } from '@endo/eventual-send';
const counter = makeCounter(10);
// Send message, get promise
const resultP = E(counter).increment(5);
const result = await resultP; // 15
// Works even if counter is a promise
const counterP = Promise.resolve(counter);
const result2 = await E(counterP).increment(3); // 18
Key property: Works uniformly whether the target is:
All calls return promises, even for local objects, ensuring consistent async behavior throughout your codebase.
Eventual get: retrieve a property, returning a promise for its value.
const config = harden({
timeout: 5000,
retries: 3
});
const timeoutP = E.get(config).timeout;
const timeout = await timeoutP; // 5000
Useful for accessing properties on remote objects or promises.
Fire-and-forget: send a message without waiting for or receiving the result.
Returns undefined immediately.
const logger = makeLogger();
// Send log message, don't wait for result
E.sendOnly(logger).log('Event occurred');
// Continues immediately, logging happens eventually
When to use:
Note: You won't get errors if the method fails.
Use regular E() if you need error handling.
Shorthand for promise handling with turn tracking:
E.when(
E(counter).getValue(),
value => console.log('Value:', value),
error => console.error('Error:', error)
);
// Equivalent to:
E(counter).getValue().then(
value => console.log('Value:', value),
error => console.error('Error:', error)
);
Primarily useful in contexts that need explicit turn tracking for debugging.
Convert a value to a handled promise:
const promise = E.resolve(value);
// promise is a HandledPromise wrapping value
Usually not needed directly; E() handles this automatically.
One of the most powerful features is promise pipelining: the ability to send messages to promises before they resolve.
import { E } from '@endo/eventual-send';
// All of these send immediately - no waiting!
const mintP = E(bootstrap).getMint();
const purseP = E(mintP).makePurse();
const paymentP = E(purseP).withdraw(100);
await E(receiverPurse).deposit(100, paymentP);
// Only wait at the end for the final result
Without pipelining, you'd need to await each step:
// Without pipelining: 4 round trips
const mint = await bootstrap.getMint(); // wait
const purse = await mint.makePurse(); // wait
const payment = await purse.withdraw(100); // wait
await receiverPurse.deposit(100, payment); // wait
// With pipelining: messages sent immediately, only wait at end
This can dramatically reduce latency in distributed systems by eliminating round trips.
How it works:
Eventual send provides four key benefits:
The same code works whether the target is local or remote:
// This code works identically whether counter is:
// - A local object
// - In a different vat on the same machine
// - On a different machine across the network
const result = await E(counter).increment(5);
Write local code, deploy distributed, no changes needed.
Messages to the same target are delivered and processed in send order:
E(counter).increment(1); // executed first
E(counter).increment(2); // executed second
E(counter).increment(3); // executed third
// Order is guaranteed
This simplifies reasoning about concurrency.
As shown above, eliminates round trips in distributed systems.
Code written with E() works locally today and distributed tomorrow:
// Works in development (local)
const result = await E(service).getData();
// Same code works in production (distributed)
// No changes needed when service moves to another vat/machine
Exos (from @endo/exo) are the ideal targets for eventual send:
import { makeExo } from '@endo/exo';
import { M } from '@endo/patterns';
import { E } from '@endo/eventual-send';
const CounterI = M.interface('Counter', {
increment: M.call(M.number()).returns(M.number())
});
const counter = makeExo('Counter', CounterI, {
increment(n) {
return count += n;
}
});
// E() provides async wrapper
const resultP = E(counter).increment(5);
// The InterfaceGuard validates n is a number
// Even if counter is remote, validation happens on receive
Even for local exos, using E() provides benefits:
Under the hood, E() uses HandledPromise, a Promise subclass that supports
handler-based dispatch:
import { HandledPromise } from '@endo/eventual-send';
// HandledPromise extends native Promise
const hp = new HandledPromise((resolve, reject, resolveWithPresence) => {
// Three ways to settle the promise
resolve(value); // Normal resolution
reject(reason); // Rejection
resolveWithPresence(h); // Resolve with a remote presence
}, handler);
// Handler intercepts operations
const handler = {
get(target, prop) { /* ... */ },
applyMethod(target, verb, args) { /* ... */ }
};
Most users don't need to use HandledPromise directly.
The E() proxy provides the ergonomic interface.
Use E() even in unit tests for consistency:
import test from 'ava';
import { E } from '@endo/eventual-send';
test('counter increments correctly', async t => {
const counter = makeCounter(0);
// Use E() even though counter is local
const result = await E(counter).increment(5);
t.is(result, 5);
});
Benefits:
E()Complete Tutorial: See Message Passing for a comprehensive guide showing how eventual-send works with pass-style, patterns, and exo to enable safe distributed computing.
This package implements the ECMAScript eventual-send proposal, which provides native language support for eventual send operations.