Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@agoric/eventual-send

Package Overview
Dependencies
Maintainers
5
Versions
322
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@agoric/eventual-send - npm Package Compare versions

Comparing version 0.7.0 to 0.8.0

24

CHANGELOG.md

@@ -6,2 +6,26 @@ # Change Log

# [0.8.0](https://github.com/Agoric/agoric-sdk/compare/@agoric/eventual-send@0.8.0-alpha.0...@agoric/eventual-send@0.8.0) (2020-04-13)
**Note:** Version bump only for package @agoric/eventual-send
# [0.8.0-alpha.0](https://github.com/Agoric/agoric-sdk/compare/@agoric/eventual-send@0.7.0...@agoric/eventual-send@0.8.0-alpha.0) (2020-04-12)
### Bug Fixes
* shorten HandledPromises to propagate handlers ([2ed50d2](https://github.com/Agoric/agoric-sdk/commit/2ed50d24c1b80959748bcaf0d04f1c4cd25f4242))
### Features
* add the returnedP as the last argument to the handler ([1f83d99](https://github.com/Agoric/agoric-sdk/commit/1f83d994f48b659f3c49c4b5eb2b50ea7bb7b7a3))
# [0.7.0](https://github.com/Agoric/agoric-sdk/compare/@agoric/eventual-send@0.7.0-alpha.0...@agoric/eventual-send@0.7.0) (2020-04-02)

@@ -8,0 +32,0 @@

266

dist/eventual-send.cjs.js

@@ -130,4 +130,5 @@ 'use strict';

let presenceToPromise;
let promiseToHandler;
let promiseToUnsettledHandler;
let promiseToPresence; // only for HandledPromise.unwrap
let forwardedPromiseToPromise; // forwarding, union-find-ish
function ensureMaps() {

@@ -137,7 +138,52 @@ if (!presenceToHandler) {

presenceToPromise = new WeakMap();
promiseToHandler = new WeakMap();
promiseToUnsettledHandler = new WeakMap();
promiseToPresence = new WeakMap();
forwardedPromiseToPromise = new WeakMap();
}
}
/**
* You can imagine a forest of trees in which the roots of each tree is an
* unresolved HandledPromise or a non-Promise, and each node's parent is the
* HandledPromise to which it was forwarded. We maintain that mapping of
* forwarded HandledPromise to its resolution in forwardedPromiseToPromise.
*
* We use something like the description of "Find" with "Path splitting"
* to propagate changes down to the children efficiently:
* https://en.wikipedia.org/wiki/Disjoint-set_data_structure
*
* @param {*} target Any value.
* @returns {*} If the target was a HandledPromise, the most-resolved parent of it, otherwise the target.
*/
function shorten(target) {
let p = target;
// Find the most-resolved value for p.
while (forwardedPromiseToPromise.has(p)) {
p = forwardedPromiseToPromise.get(p);
}
const presence = promiseToPresence.get(p);
if (presence) {
// Presences are final, so it is ok to propagate
// this upstream.
while (target !== p) {
const parent = forwardedPromiseToPromise.get(target);
forwardedPromiseToPromise.delete(target);
promiseToUnsettledHandler.delete(target);
promiseToPresence.set(target, presence);
target = parent;
}
} else {
// We propagate p and remove all other unsettled handlers
// upstream.
// Note that everything except presences is covered here.
while (target !== p) {
const parent = forwardedPromiseToPromise.get(target);
forwardedPromiseToPromise.set(target, p);
promiseToUnsettledHandler.delete(target);
target = parent;
}
}
return target;
}
// This special handler accepts Promises, and forwards

@@ -149,3 +195,3 @@ // handled Promises to their corresponding fulfilledHandler.

function HandledPromise(executor, unfulfilledHandler = undefined) {
function HandledPromise(executor, unsettledHandler = undefined) {
if (new.target === undefined) {

@@ -156,43 +202,76 @@ throw new Error('must be invoked with "new"');

let handledReject;
let fulfilled = false;
const superExecutor = (resolve, reject) => {
let resolved = false;
let resolvedTarget = null;
let handledP;
let continueForwarding = () => {};
const superExecutor = (superResolve, superReject) => {
handledResolve = value => {
fulfilled = true;
resolve(value);
if (resolved) {
return resolvedTarget;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
value = shorten(value);
let targetP;
if (
promiseToUnsettledHandler.has(value) ||
promiseToPresence.has(value)
) {
targetP = value;
} else {
// We're resolving to a non-promise, so remove our handler.
promiseToUnsettledHandler.delete(handledP);
targetP = presenceToPromise.get(value);
}
// Ensure our data structure is a propert tree (avoid cycles).
if (targetP && targetP !== handledP) {
forwardedPromiseToPromise.set(handledP, targetP);
} else {
forwardedPromiseToPromise.delete(handledP);
}
// Remove stale unsettled handlers, set to canonical form.
shorten(handledP);
// Ensure our unsettledHandler is cleaned up if not already.
if (promiseToUnsettledHandler.has(handledP)) {
handledP.then(_ => promiseToUnsettledHandler.delete(handledP));
}
// Finish the resolution.
superResolve(value);
resolved = true;
resolvedTarget = value;
// We're resolved, so forward any postponed operations to us.
continueForwarding();
return resolvedTarget;
};
handledReject = err => {
fulfilled = true;
reject(err);
if (resolved) {
return;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
promiseToUnsettledHandler.delete(handledP);
resolved = true;
superReject(err);
continueForwarding();
};
};
const handledP = harden(
Reflect.construct(Promise, [superExecutor], new.target),
);
handledP = harden(Reflect.construct(Promise, [superExecutor], new.target));
ensureMaps();
let continueForwarding = () => {};
if (!unfulfilledHandler) {
// Create a simple unfulfilledHandler that just postpones until the
const makePostponedHandler = () => {
// Create a simple postponedHandler that just postpones until the
// fulfilledHandler is set.
//
// This is insufficient for actual remote handled Promises
// (too many round-trips), but is an easy way to create a
// local handled Promise.
const interlockP = new Promise((resolve, reject) => {
continueForwarding = (err = null, targetP = undefined) => {
if (err !== null) {
reject(err);
return;
}
// Box the target promise so that it isn't further resolved.
resolve([targetP]);
// Return undefined.
};
let donePostponing;
const interlockP = new Promise(resolve => {
donePostponing = () => resolve();
});
// A failed interlock should not be recorded as an unhandled rejection.
// It will bubble up to the HandledPromise itself.
interlockP.catch(_ => {});
const makePostponed = postponedOperation => {
const makePostponedOperation = postponedOperation => {
// Just wait until the handler is resolved/rejected.

@@ -203,8 +282,5 @@ return function postpone(x, ...args) {

interlockP
.then(([targetP]) => {
.then(_ => {
// If targetP is a handled promise, use it, otherwise x.
const nextPromise = targetP || x;
resolve(
HandledPromise[postponedOperation](nextPromise, ...args),
);
resolve(HandledPromise[postponedOperation](x, ...args));
})

@@ -216,6 +292,14 @@ .catch(reject);

unfulfilledHandler = {
get: makePostponed('get'),
applyMethod: makePostponed('applyMethod'),
const postponedHandler = {
get: makePostponedOperation('get'),
applyMethod: makePostponedOperation('applyMethod'),
};
return [postponedHandler, donePostponing];
};
if (!unsettledHandler) {
// This is insufficient for actual remote handled Promises
// (too many round-trips), but is an easy way to create a
// local handled Promise.
[unsettledHandler, continueForwarding] = makePostponedHandler();
}

@@ -228,20 +312,24 @@

};
validateHandler(unfulfilledHandler);
validateHandler(unsettledHandler);
// Until the handled promise is resolved, we use the unfulfilledHandler.
promiseToHandler.set(handledP, unfulfilledHandler);
// Until the handled promise is resolved, we use the unsettledHandler.
promiseToUnsettledHandler.set(handledP, unsettledHandler);
const rejectHandled = reason => {
if (fulfilled) {
if (resolved) {
return;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
handledReject(reason);
continueForwarding(reason);
};
let resolvedPresence = null;
const resolveWithPresence = presenceHandler => {
if (fulfilled) {
return resolvedPresence;
if (resolved) {
return resolvedTarget;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
try {

@@ -252,21 +340,15 @@ // Sanity checks.

// Validate and install our mapped target (i.e. presence).
resolvedPresence = Object.create(null);
resolvedTarget = Object.create(null);
// Create table entries for the presence mapped to the
// fulfilledHandler.
presenceToPromise.set(resolvedPresence, handledP);
promiseToPresence.set(handledP, resolvedPresence);
presenceToHandler.set(resolvedPresence, presenceHandler);
presenceToPromise.set(resolvedTarget, handledP);
promiseToPresence.set(handledP, resolvedTarget);
presenceToHandler.set(resolvedTarget, presenceHandler);
// Remove the mapping, as our presenceHandler should be
// used instead.
promiseToHandler.delete(handledP);
// We committed to this presence, so resolve.
handledResolve(resolvedPresence);
continueForwarding();
return resolvedPresence;
handledResolve(resolvedTarget);
return resolvedTarget;
} catch (e) {
handledReject(e);
continueForwarding();
throw e;

@@ -277,5 +359,8 @@ }

const resolveHandled = async (target, deprecatedPresenceHandler) => {
if (fulfilled) {
return undefined;
if (resolved) {
return;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
try {

@@ -288,33 +373,7 @@ if (deprecatedPresenceHandler) {

// Resolve with the target when it's ready.
// Resolve the target.
handledResolve(target);
const existingUnfulfilledHandler = promiseToHandler.get(target);
if (existingUnfulfilledHandler) {
// Reuse the unfulfilled handler.
promiseToHandler.set(handledP, existingUnfulfilledHandler);
return continueForwarding(null, target);
}
// See if the target is a presence we already know of.
let presence;
try {
presence = HandledPromise.unwrap(target);
} catch (e) {
presence = await target;
}
const existingPresenceHandler = presenceToHandler.get(presence);
if (existingPresenceHandler) {
promiseToHandler.set(handledP, existingPresenceHandler);
promiseToPresence.set(handledP, presence);
return continueForwarding(null, handledP);
}
// Remove the mapping, as we don't need a handler.
promiseToHandler.delete(handledP);
return continueForwarding();
} catch (e) {
handledReject(e);
}
return continueForwarding();
};

@@ -457,10 +516,8 @@

handle = (p, operation, ...args) => {
handle = (p, operation, ...opArgs) => {
ensureMaps();
const unfulfilledHandler = promiseToHandler.get(p);
p = shorten(p);
const unsettledHandler = promiseToUnsettledHandler.get(p);
let executor;
if (
unfulfilledHandler &&
typeof unfulfilledHandler[operation] === 'function'
) {
if (unsettledHandler && typeof unsettledHandler[operation] === 'function') {
executor = (resolve, reject) => {

@@ -470,4 +527,7 @@ // We run in a future turn to prevent synchronous attacks,

.then(() =>
// and resolve to the answer from the specific unfulfilled handler,
resolve(unfulfilledHandler[operation](p, ...args)),
// and resolve to the answer from the specific unsettled handler,
// opArgs are something like [prop] or [method, args],
// so we don't risk the user's args leaking into this expansion.
// eslint-disable-next-line no-use-before-define
resolve(unsettledHandler[operation](p, ...opArgs, returnedP)),
)

@@ -488,3 +548,6 @@ .catch(reject);

// and resolve to the forwardingHandler's operation.
resolve(forwardingHandler[operation](o, ...args));
// opArgs are something like [prop] or [method, args],
// so we don't risk the user's args leaking into this expansion.
// eslint-disable-next-line no-use-before-define
resolve(forwardingHandler[operation](o, ...opArgs, returnedP));
})

@@ -495,6 +558,7 @@ .catch(reject);

// We return a handled promise with the default unfulfilled handler.
// We return a handled promise with the default unsettled handler.
// This prevents a race between the above Promise.resolves and
// pipelining.
return new HandledPromise(executor);
const returnedP = new HandledPromise(executor);
return returnedP;
};

@@ -501,0 +565,0 @@

@@ -124,4 +124,5 @@ import harden from '@agoric/harden';

let presenceToPromise;
let promiseToHandler;
let promiseToUnsettledHandler;
let promiseToPresence; // only for HandledPromise.unwrap
let forwardedPromiseToPromise; // forwarding, union-find-ish
function ensureMaps() {

@@ -131,7 +132,52 @@ if (!presenceToHandler) {

presenceToPromise = new WeakMap();
promiseToHandler = new WeakMap();
promiseToUnsettledHandler = new WeakMap();
promiseToPresence = new WeakMap();
forwardedPromiseToPromise = new WeakMap();
}
}
/**
* You can imagine a forest of trees in which the roots of each tree is an
* unresolved HandledPromise or a non-Promise, and each node's parent is the
* HandledPromise to which it was forwarded. We maintain that mapping of
* forwarded HandledPromise to its resolution in forwardedPromiseToPromise.
*
* We use something like the description of "Find" with "Path splitting"
* to propagate changes down to the children efficiently:
* https://en.wikipedia.org/wiki/Disjoint-set_data_structure
*
* @param {*} target Any value.
* @returns {*} If the target was a HandledPromise, the most-resolved parent of it, otherwise the target.
*/
function shorten(target) {
let p = target;
// Find the most-resolved value for p.
while (forwardedPromiseToPromise.has(p)) {
p = forwardedPromiseToPromise.get(p);
}
const presence = promiseToPresence.get(p);
if (presence) {
// Presences are final, so it is ok to propagate
// this upstream.
while (target !== p) {
const parent = forwardedPromiseToPromise.get(target);
forwardedPromiseToPromise.delete(target);
promiseToUnsettledHandler.delete(target);
promiseToPresence.set(target, presence);
target = parent;
}
} else {
// We propagate p and remove all other unsettled handlers
// upstream.
// Note that everything except presences is covered here.
while (target !== p) {
const parent = forwardedPromiseToPromise.get(target);
forwardedPromiseToPromise.set(target, p);
promiseToUnsettledHandler.delete(target);
target = parent;
}
}
return target;
}
// This special handler accepts Promises, and forwards

@@ -143,3 +189,3 @@ // handled Promises to their corresponding fulfilledHandler.

function HandledPromise(executor, unfulfilledHandler = undefined) {
function HandledPromise(executor, unsettledHandler = undefined) {
if (new.target === undefined) {

@@ -150,43 +196,76 @@ throw new Error('must be invoked with "new"');

let handledReject;
let fulfilled = false;
const superExecutor = (resolve, reject) => {
let resolved = false;
let resolvedTarget = null;
let handledP;
let continueForwarding = () => {};
const superExecutor = (superResolve, superReject) => {
handledResolve = value => {
fulfilled = true;
resolve(value);
if (resolved) {
return resolvedTarget;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
value = shorten(value);
let targetP;
if (
promiseToUnsettledHandler.has(value) ||
promiseToPresence.has(value)
) {
targetP = value;
} else {
// We're resolving to a non-promise, so remove our handler.
promiseToUnsettledHandler.delete(handledP);
targetP = presenceToPromise.get(value);
}
// Ensure our data structure is a propert tree (avoid cycles).
if (targetP && targetP !== handledP) {
forwardedPromiseToPromise.set(handledP, targetP);
} else {
forwardedPromiseToPromise.delete(handledP);
}
// Remove stale unsettled handlers, set to canonical form.
shorten(handledP);
// Ensure our unsettledHandler is cleaned up if not already.
if (promiseToUnsettledHandler.has(handledP)) {
handledP.then(_ => promiseToUnsettledHandler.delete(handledP));
}
// Finish the resolution.
superResolve(value);
resolved = true;
resolvedTarget = value;
// We're resolved, so forward any postponed operations to us.
continueForwarding();
return resolvedTarget;
};
handledReject = err => {
fulfilled = true;
reject(err);
if (resolved) {
return;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
promiseToUnsettledHandler.delete(handledP);
resolved = true;
superReject(err);
continueForwarding();
};
};
const handledP = harden(
Reflect.construct(Promise, [superExecutor], new.target),
);
handledP = harden(Reflect.construct(Promise, [superExecutor], new.target));
ensureMaps();
let continueForwarding = () => {};
if (!unfulfilledHandler) {
// Create a simple unfulfilledHandler that just postpones until the
const makePostponedHandler = () => {
// Create a simple postponedHandler that just postpones until the
// fulfilledHandler is set.
//
// This is insufficient for actual remote handled Promises
// (too many round-trips), but is an easy way to create a
// local handled Promise.
const interlockP = new Promise((resolve, reject) => {
continueForwarding = (err = null, targetP = undefined) => {
if (err !== null) {
reject(err);
return;
}
// Box the target promise so that it isn't further resolved.
resolve([targetP]);
// Return undefined.
};
let donePostponing;
const interlockP = new Promise(resolve => {
donePostponing = () => resolve();
});
// A failed interlock should not be recorded as an unhandled rejection.
// It will bubble up to the HandledPromise itself.
interlockP.catch(_ => {});
const makePostponed = postponedOperation => {
const makePostponedOperation = postponedOperation => {
// Just wait until the handler is resolved/rejected.

@@ -197,8 +276,5 @@ return function postpone(x, ...args) {

interlockP
.then(([targetP]) => {
.then(_ => {
// If targetP is a handled promise, use it, otherwise x.
const nextPromise = targetP || x;
resolve(
HandledPromise[postponedOperation](nextPromise, ...args),
);
resolve(HandledPromise[postponedOperation](x, ...args));
})

@@ -210,6 +286,14 @@ .catch(reject);

unfulfilledHandler = {
get: makePostponed('get'),
applyMethod: makePostponed('applyMethod'),
const postponedHandler = {
get: makePostponedOperation('get'),
applyMethod: makePostponedOperation('applyMethod'),
};
return [postponedHandler, donePostponing];
};
if (!unsettledHandler) {
// This is insufficient for actual remote handled Promises
// (too many round-trips), but is an easy way to create a
// local handled Promise.
[unsettledHandler, continueForwarding] = makePostponedHandler();
}

@@ -222,20 +306,24 @@

};
validateHandler(unfulfilledHandler);
validateHandler(unsettledHandler);
// Until the handled promise is resolved, we use the unfulfilledHandler.
promiseToHandler.set(handledP, unfulfilledHandler);
// Until the handled promise is resolved, we use the unsettledHandler.
promiseToUnsettledHandler.set(handledP, unsettledHandler);
const rejectHandled = reason => {
if (fulfilled) {
if (resolved) {
return;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
handledReject(reason);
continueForwarding(reason);
};
let resolvedPresence = null;
const resolveWithPresence = presenceHandler => {
if (fulfilled) {
return resolvedPresence;
if (resolved) {
return resolvedTarget;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
try {

@@ -246,21 +334,15 @@ // Sanity checks.

// Validate and install our mapped target (i.e. presence).
resolvedPresence = Object.create(null);
resolvedTarget = Object.create(null);
// Create table entries for the presence mapped to the
// fulfilledHandler.
presenceToPromise.set(resolvedPresence, handledP);
promiseToPresence.set(handledP, resolvedPresence);
presenceToHandler.set(resolvedPresence, presenceHandler);
presenceToPromise.set(resolvedTarget, handledP);
promiseToPresence.set(handledP, resolvedTarget);
presenceToHandler.set(resolvedTarget, presenceHandler);
// Remove the mapping, as our presenceHandler should be
// used instead.
promiseToHandler.delete(handledP);
// We committed to this presence, so resolve.
handledResolve(resolvedPresence);
continueForwarding();
return resolvedPresence;
handledResolve(resolvedTarget);
return resolvedTarget;
} catch (e) {
handledReject(e);
continueForwarding();
throw e;

@@ -271,5 +353,8 @@ }

const resolveHandled = async (target, deprecatedPresenceHandler) => {
if (fulfilled) {
return undefined;
if (resolved) {
return;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
try {

@@ -282,33 +367,7 @@ if (deprecatedPresenceHandler) {

// Resolve with the target when it's ready.
// Resolve the target.
handledResolve(target);
const existingUnfulfilledHandler = promiseToHandler.get(target);
if (existingUnfulfilledHandler) {
// Reuse the unfulfilled handler.
promiseToHandler.set(handledP, existingUnfulfilledHandler);
return continueForwarding(null, target);
}
// See if the target is a presence we already know of.
let presence;
try {
presence = HandledPromise.unwrap(target);
} catch (e) {
presence = await target;
}
const existingPresenceHandler = presenceToHandler.get(presence);
if (existingPresenceHandler) {
promiseToHandler.set(handledP, existingPresenceHandler);
promiseToPresence.set(handledP, presence);
return continueForwarding(null, handledP);
}
// Remove the mapping, as we don't need a handler.
promiseToHandler.delete(handledP);
return continueForwarding();
} catch (e) {
handledReject(e);
}
return continueForwarding();
};

@@ -451,10 +510,8 @@

handle = (p, operation, ...args) => {
handle = (p, operation, ...opArgs) => {
ensureMaps();
const unfulfilledHandler = promiseToHandler.get(p);
p = shorten(p);
const unsettledHandler = promiseToUnsettledHandler.get(p);
let executor;
if (
unfulfilledHandler &&
typeof unfulfilledHandler[operation] === 'function'
) {
if (unsettledHandler && typeof unsettledHandler[operation] === 'function') {
executor = (resolve, reject) => {

@@ -464,4 +521,7 @@ // We run in a future turn to prevent synchronous attacks,

.then(() =>
// and resolve to the answer from the specific unfulfilled handler,
resolve(unfulfilledHandler[operation](p, ...args)),
// and resolve to the answer from the specific unsettled handler,
// opArgs are something like [prop] or [method, args],
// so we don't risk the user's args leaking into this expansion.
// eslint-disable-next-line no-use-before-define
resolve(unsettledHandler[operation](p, ...opArgs, returnedP)),
)

@@ -482,3 +542,6 @@ .catch(reject);

// and resolve to the forwardingHandler's operation.
resolve(forwardingHandler[operation](o, ...args));
// opArgs are something like [prop] or [method, args],
// so we don't risk the user's args leaking into this expansion.
// eslint-disable-next-line no-use-before-define
resolve(forwardingHandler[operation](o, ...opArgs, returnedP));
})

@@ -489,6 +552,7 @@ .catch(reject);

// We return a handled promise with the default unfulfilled handler.
// We return a handled promise with the default unsettled handler.
// This prevents a race between the above Promise.resolves and
// pipelining.
return new HandledPromise(executor);
const returnedP = new HandledPromise(executor);
return returnedP;
};

@@ -495,0 +559,0 @@

@@ -130,4 +130,5 @@ (function (global, factory) {

let presenceToPromise;
let promiseToHandler;
let promiseToUnsettledHandler;
let promiseToPresence; // only for HandledPromise.unwrap
let forwardedPromiseToPromise; // forwarding, union-find-ish
function ensureMaps() {

@@ -137,7 +138,52 @@ if (!presenceToHandler) {

presenceToPromise = new WeakMap();
promiseToHandler = new WeakMap();
promiseToUnsettledHandler = new WeakMap();
promiseToPresence = new WeakMap();
forwardedPromiseToPromise = new WeakMap();
}
}
/**
* You can imagine a forest of trees in which the roots of each tree is an
* unresolved HandledPromise or a non-Promise, and each node's parent is the
* HandledPromise to which it was forwarded. We maintain that mapping of
* forwarded HandledPromise to its resolution in forwardedPromiseToPromise.
*
* We use something like the description of "Find" with "Path splitting"
* to propagate changes down to the children efficiently:
* https://en.wikipedia.org/wiki/Disjoint-set_data_structure
*
* @param {*} target Any value.
* @returns {*} If the target was a HandledPromise, the most-resolved parent of it, otherwise the target.
*/
function shorten(target) {
let p = target;
// Find the most-resolved value for p.
while (forwardedPromiseToPromise.has(p)) {
p = forwardedPromiseToPromise.get(p);
}
const presence = promiseToPresence.get(p);
if (presence) {
// Presences are final, so it is ok to propagate
// this upstream.
while (target !== p) {
const parent = forwardedPromiseToPromise.get(target);
forwardedPromiseToPromise.delete(target);
promiseToUnsettledHandler.delete(target);
promiseToPresence.set(target, presence);
target = parent;
}
} else {
// We propagate p and remove all other unsettled handlers
// upstream.
// Note that everything except presences is covered here.
while (target !== p) {
const parent = forwardedPromiseToPromise.get(target);
forwardedPromiseToPromise.set(target, p);
promiseToUnsettledHandler.delete(target);
target = parent;
}
}
return target;
}
// This special handler accepts Promises, and forwards

@@ -149,3 +195,3 @@ // handled Promises to their corresponding fulfilledHandler.

function HandledPromise(executor, unfulfilledHandler = undefined) {
function HandledPromise(executor, unsettledHandler = undefined) {
if (new.target === undefined) {

@@ -156,43 +202,76 @@ throw new Error('must be invoked with "new"');

let handledReject;
let fulfilled = false;
const superExecutor = (resolve, reject) => {
let resolved = false;
let resolvedTarget = null;
let handledP;
let continueForwarding = () => {};
const superExecutor = (superResolve, superReject) => {
handledResolve = value => {
fulfilled = true;
resolve(value);
if (resolved) {
return resolvedTarget;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
value = shorten(value);
let targetP;
if (
promiseToUnsettledHandler.has(value) ||
promiseToPresence.has(value)
) {
targetP = value;
} else {
// We're resolving to a non-promise, so remove our handler.
promiseToUnsettledHandler.delete(handledP);
targetP = presenceToPromise.get(value);
}
// Ensure our data structure is a propert tree (avoid cycles).
if (targetP && targetP !== handledP) {
forwardedPromiseToPromise.set(handledP, targetP);
} else {
forwardedPromiseToPromise.delete(handledP);
}
// Remove stale unsettled handlers, set to canonical form.
shorten(handledP);
// Ensure our unsettledHandler is cleaned up if not already.
if (promiseToUnsettledHandler.has(handledP)) {
handledP.then(_ => promiseToUnsettledHandler.delete(handledP));
}
// Finish the resolution.
superResolve(value);
resolved = true;
resolvedTarget = value;
// We're resolved, so forward any postponed operations to us.
continueForwarding();
return resolvedTarget;
};
handledReject = err => {
fulfilled = true;
reject(err);
if (resolved) {
return;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
promiseToUnsettledHandler.delete(handledP);
resolved = true;
superReject(err);
continueForwarding();
};
};
const handledP = harden(
Reflect.construct(Promise, [superExecutor], new.target),
);
handledP = harden(Reflect.construct(Promise, [superExecutor], new.target));
ensureMaps();
let continueForwarding = () => {};
if (!unfulfilledHandler) {
// Create a simple unfulfilledHandler that just postpones until the
const makePostponedHandler = () => {
// Create a simple postponedHandler that just postpones until the
// fulfilledHandler is set.
//
// This is insufficient for actual remote handled Promises
// (too many round-trips), but is an easy way to create a
// local handled Promise.
const interlockP = new Promise((resolve, reject) => {
continueForwarding = (err = null, targetP = undefined) => {
if (err !== null) {
reject(err);
return;
}
// Box the target promise so that it isn't further resolved.
resolve([targetP]);
// Return undefined.
};
let donePostponing;
const interlockP = new Promise(resolve => {
donePostponing = () => resolve();
});
// A failed interlock should not be recorded as an unhandled rejection.
// It will bubble up to the HandledPromise itself.
interlockP.catch(_ => {});
const makePostponed = postponedOperation => {
const makePostponedOperation = postponedOperation => {
// Just wait until the handler is resolved/rejected.

@@ -203,8 +282,5 @@ return function postpone(x, ...args) {

interlockP
.then(([targetP]) => {
.then(_ => {
// If targetP is a handled promise, use it, otherwise x.
const nextPromise = targetP || x;
resolve(
HandledPromise[postponedOperation](nextPromise, ...args),
);
resolve(HandledPromise[postponedOperation](x, ...args));
})

@@ -216,6 +292,14 @@ .catch(reject);

unfulfilledHandler = {
get: makePostponed('get'),
applyMethod: makePostponed('applyMethod'),
const postponedHandler = {
get: makePostponedOperation('get'),
applyMethod: makePostponedOperation('applyMethod'),
};
return [postponedHandler, donePostponing];
};
if (!unsettledHandler) {
// This is insufficient for actual remote handled Promises
// (too many round-trips), but is an easy way to create a
// local handled Promise.
[unsettledHandler, continueForwarding] = makePostponedHandler();
}

@@ -228,20 +312,24 @@

};
validateHandler(unfulfilledHandler);
validateHandler(unsettledHandler);
// Until the handled promise is resolved, we use the unfulfilledHandler.
promiseToHandler.set(handledP, unfulfilledHandler);
// Until the handled promise is resolved, we use the unsettledHandler.
promiseToUnsettledHandler.set(handledP, unsettledHandler);
const rejectHandled = reason => {
if (fulfilled) {
if (resolved) {
return;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
handledReject(reason);
continueForwarding(reason);
};
let resolvedPresence = null;
const resolveWithPresence = presenceHandler => {
if (fulfilled) {
return resolvedPresence;
if (resolved) {
return resolvedTarget;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
try {

@@ -252,21 +340,15 @@ // Sanity checks.

// Validate and install our mapped target (i.e. presence).
resolvedPresence = Object.create(null);
resolvedTarget = Object.create(null);
// Create table entries for the presence mapped to the
// fulfilledHandler.
presenceToPromise.set(resolvedPresence, handledP);
promiseToPresence.set(handledP, resolvedPresence);
presenceToHandler.set(resolvedPresence, presenceHandler);
presenceToPromise.set(resolvedTarget, handledP);
promiseToPresence.set(handledP, resolvedTarget);
presenceToHandler.set(resolvedTarget, presenceHandler);
// Remove the mapping, as our presenceHandler should be
// used instead.
promiseToHandler.delete(handledP);
// We committed to this presence, so resolve.
handledResolve(resolvedPresence);
continueForwarding();
return resolvedPresence;
handledResolve(resolvedTarget);
return resolvedTarget;
} catch (e) {
handledReject(e);
continueForwarding();
throw e;

@@ -277,5 +359,8 @@ }

const resolveHandled = async (target, deprecatedPresenceHandler) => {
if (fulfilled) {
return undefined;
if (resolved) {
return;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
try {

@@ -288,33 +373,7 @@ if (deprecatedPresenceHandler) {

// Resolve with the target when it's ready.
// Resolve the target.
handledResolve(target);
const existingUnfulfilledHandler = promiseToHandler.get(target);
if (existingUnfulfilledHandler) {
// Reuse the unfulfilled handler.
promiseToHandler.set(handledP, existingUnfulfilledHandler);
return continueForwarding(null, target);
}
// See if the target is a presence we already know of.
let presence;
try {
presence = HandledPromise.unwrap(target);
} catch (e) {
presence = await target;
}
const existingPresenceHandler = presenceToHandler.get(presence);
if (existingPresenceHandler) {
promiseToHandler.set(handledP, existingPresenceHandler);
promiseToPresence.set(handledP, presence);
return continueForwarding(null, handledP);
}
// Remove the mapping, as we don't need a handler.
promiseToHandler.delete(handledP);
return continueForwarding();
} catch (e) {
handledReject(e);
}
return continueForwarding();
};

@@ -457,10 +516,8 @@

handle = (p, operation, ...args) => {
handle = (p, operation, ...opArgs) => {
ensureMaps();
const unfulfilledHandler = promiseToHandler.get(p);
p = shorten(p);
const unsettledHandler = promiseToUnsettledHandler.get(p);
let executor;
if (
unfulfilledHandler &&
typeof unfulfilledHandler[operation] === 'function'
) {
if (unsettledHandler && typeof unsettledHandler[operation] === 'function') {
executor = (resolve, reject) => {

@@ -470,4 +527,7 @@ // We run in a future turn to prevent synchronous attacks,

.then(() =>
// and resolve to the answer from the specific unfulfilled handler,
resolve(unfulfilledHandler[operation](p, ...args)),
// and resolve to the answer from the specific unsettled handler,
// opArgs are something like [prop] or [method, args],
// so we don't risk the user's args leaking into this expansion.
// eslint-disable-next-line no-use-before-define
resolve(unsettledHandler[operation](p, ...opArgs, returnedP)),
)

@@ -488,3 +548,6 @@ .catch(reject);

// and resolve to the forwardingHandler's operation.
resolve(forwardingHandler[operation](o, ...args));
// opArgs are something like [prop] or [method, args],
// so we don't risk the user's args leaking into this expansion.
// eslint-disable-next-line no-use-before-define
resolve(forwardingHandler[operation](o, ...opArgs, returnedP));
})

@@ -495,6 +558,7 @@ .catch(reject);

// We return a handled promise with the default unfulfilled handler.
// We return a handled promise with the default unsettled handler.
// This prevents a race between the above Promise.resolves and
// pipelining.
return new HandledPromise(executor);
const returnedP = new HandledPromise(executor);
return returnedP;
};

@@ -501,0 +565,0 @@

{
"name": "@agoric/eventual-send",
"version": "0.7.0",
"version": "0.8.0",
"description": "Extend a Promise class to implement the eventual-send API",

@@ -48,3 +48,3 @@ "main": "dist/eventual-send.cjs.js",

},
"gitHead": "536b8e521e881357ca2945533b027ab0bd4b85e5"
"gitHead": "a5fe2624fedcf3b8adf46ed6c157c29fd459b2ed"
}

@@ -55,4 +55,5 @@ /* global HandledPromise */

let presenceToPromise;
let promiseToHandler;
let promiseToUnsettledHandler;
let promiseToPresence; // only for HandledPromise.unwrap
let forwardedPromiseToPromise; // forwarding, union-find-ish
function ensureMaps() {

@@ -62,7 +63,52 @@ if (!presenceToHandler) {

presenceToPromise = new WeakMap();
promiseToHandler = new WeakMap();
promiseToUnsettledHandler = new WeakMap();
promiseToPresence = new WeakMap();
forwardedPromiseToPromise = new WeakMap();
}
}
/**
* You can imagine a forest of trees in which the roots of each tree is an
* unresolved HandledPromise or a non-Promise, and each node's parent is the
* HandledPromise to which it was forwarded. We maintain that mapping of
* forwarded HandledPromise to its resolution in forwardedPromiseToPromise.
*
* We use something like the description of "Find" with "Path splitting"
* to propagate changes down to the children efficiently:
* https://en.wikipedia.org/wiki/Disjoint-set_data_structure
*
* @param {*} target Any value.
* @returns {*} If the target was a HandledPromise, the most-resolved parent of it, otherwise the target.
*/
function shorten(target) {
let p = target;
// Find the most-resolved value for p.
while (forwardedPromiseToPromise.has(p)) {
p = forwardedPromiseToPromise.get(p);
}
const presence = promiseToPresence.get(p);
if (presence) {
// Presences are final, so it is ok to propagate
// this upstream.
while (target !== p) {
const parent = forwardedPromiseToPromise.get(target);
forwardedPromiseToPromise.delete(target);
promiseToUnsettledHandler.delete(target);
promiseToPresence.set(target, presence);
target = parent;
}
} else {
// We propagate p and remove all other unsettled handlers
// upstream.
// Note that everything except presences is covered here.
while (target !== p) {
const parent = forwardedPromiseToPromise.get(target);
forwardedPromiseToPromise.set(target, p);
promiseToUnsettledHandler.delete(target);
target = parent;
}
}
return target;
}
// This special handler accepts Promises, and forwards

@@ -74,3 +120,3 @@ // handled Promises to their corresponding fulfilledHandler.

function HandledPromise(executor, unfulfilledHandler = undefined) {
function HandledPromise(executor, unsettledHandler = undefined) {
if (new.target === undefined) {

@@ -81,43 +127,76 @@ throw new Error('must be invoked with "new"');

let handledReject;
let fulfilled = false;
const superExecutor = (resolve, reject) => {
let resolved = false;
let resolvedTarget = null;
let handledP;
let continueForwarding = () => {};
const superExecutor = (superResolve, superReject) => {
handledResolve = value => {
fulfilled = true;
resolve(value);
if (resolved) {
return resolvedTarget;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
value = shorten(value);
let targetP;
if (
promiseToUnsettledHandler.has(value) ||
promiseToPresence.has(value)
) {
targetP = value;
} else {
// We're resolving to a non-promise, so remove our handler.
promiseToUnsettledHandler.delete(handledP);
targetP = presenceToPromise.get(value);
}
// Ensure our data structure is a propert tree (avoid cycles).
if (targetP && targetP !== handledP) {
forwardedPromiseToPromise.set(handledP, targetP);
} else {
forwardedPromiseToPromise.delete(handledP);
}
// Remove stale unsettled handlers, set to canonical form.
shorten(handledP);
// Ensure our unsettledHandler is cleaned up if not already.
if (promiseToUnsettledHandler.has(handledP)) {
handledP.then(_ => promiseToUnsettledHandler.delete(handledP));
}
// Finish the resolution.
superResolve(value);
resolved = true;
resolvedTarget = value;
// We're resolved, so forward any postponed operations to us.
continueForwarding();
return resolvedTarget;
};
handledReject = err => {
fulfilled = true;
reject(err);
if (resolved) {
return;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
promiseToUnsettledHandler.delete(handledP);
resolved = true;
superReject(err);
continueForwarding();
};
};
const handledP = harden(
Reflect.construct(Promise, [superExecutor], new.target),
);
handledP = harden(Reflect.construct(Promise, [superExecutor], new.target));
ensureMaps();
let continueForwarding = () => {};
if (!unfulfilledHandler) {
// Create a simple unfulfilledHandler that just postpones until the
const makePostponedHandler = () => {
// Create a simple postponedHandler that just postpones until the
// fulfilledHandler is set.
//
// This is insufficient for actual remote handled Promises
// (too many round-trips), but is an easy way to create a
// local handled Promise.
const interlockP = new Promise((resolve, reject) => {
continueForwarding = (err = null, targetP = undefined) => {
if (err !== null) {
reject(err);
return;
}
// Box the target promise so that it isn't further resolved.
resolve([targetP]);
// Return undefined.
};
let donePostponing;
const interlockP = new Promise(resolve => {
donePostponing = () => resolve();
});
// A failed interlock should not be recorded as an unhandled rejection.
// It will bubble up to the HandledPromise itself.
interlockP.catch(_ => {});
const makePostponed = postponedOperation => {
const makePostponedOperation = postponedOperation => {
// Just wait until the handler is resolved/rejected.

@@ -128,8 +207,5 @@ return function postpone(x, ...args) {

interlockP
.then(([targetP]) => {
.then(_ => {
// If targetP is a handled promise, use it, otherwise x.
const nextPromise = targetP || x;
resolve(
HandledPromise[postponedOperation](nextPromise, ...args),
);
resolve(HandledPromise[postponedOperation](x, ...args));
})

@@ -141,6 +217,14 @@ .catch(reject);

unfulfilledHandler = {
get: makePostponed('get'),
applyMethod: makePostponed('applyMethod'),
const postponedHandler = {
get: makePostponedOperation('get'),
applyMethod: makePostponedOperation('applyMethod'),
};
return [postponedHandler, donePostponing];
};
if (!unsettledHandler) {
// This is insufficient for actual remote handled Promises
// (too many round-trips), but is an easy way to create a
// local handled Promise.
[unsettledHandler, continueForwarding] = makePostponedHandler();
}

@@ -153,20 +237,24 @@

};
validateHandler(unfulfilledHandler);
validateHandler(unsettledHandler);
// Until the handled promise is resolved, we use the unfulfilledHandler.
promiseToHandler.set(handledP, unfulfilledHandler);
// Until the handled promise is resolved, we use the unsettledHandler.
promiseToUnsettledHandler.set(handledP, unsettledHandler);
const rejectHandled = reason => {
if (fulfilled) {
if (resolved) {
return;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
handledReject(reason);
continueForwarding(reason);
};
let resolvedPresence = null;
const resolveWithPresence = presenceHandler => {
if (fulfilled) {
return resolvedPresence;
if (resolved) {
return resolvedTarget;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
try {

@@ -177,21 +265,15 @@ // Sanity checks.

// Validate and install our mapped target (i.e. presence).
resolvedPresence = Object.create(null);
resolvedTarget = Object.create(null);
// Create table entries for the presence mapped to the
// fulfilledHandler.
presenceToPromise.set(resolvedPresence, handledP);
promiseToPresence.set(handledP, resolvedPresence);
presenceToHandler.set(resolvedPresence, presenceHandler);
presenceToPromise.set(resolvedTarget, handledP);
promiseToPresence.set(handledP, resolvedTarget);
presenceToHandler.set(resolvedTarget, presenceHandler);
// Remove the mapping, as our presenceHandler should be
// used instead.
promiseToHandler.delete(handledP);
// We committed to this presence, so resolve.
handledResolve(resolvedPresence);
continueForwarding();
return resolvedPresence;
handledResolve(resolvedTarget);
return resolvedTarget;
} catch (e) {
handledReject(e);
continueForwarding();
throw e;

@@ -202,5 +284,8 @@ }

const resolveHandled = async (target, deprecatedPresenceHandler) => {
if (fulfilled) {
return undefined;
if (resolved) {
return;
}
if (forwardedPromiseToPromise.has(handledP)) {
throw new TypeError('internal: already forwarded');
}
try {

@@ -213,33 +298,7 @@ if (deprecatedPresenceHandler) {

// Resolve with the target when it's ready.
// Resolve the target.
handledResolve(target);
const existingUnfulfilledHandler = promiseToHandler.get(target);
if (existingUnfulfilledHandler) {
// Reuse the unfulfilled handler.
promiseToHandler.set(handledP, existingUnfulfilledHandler);
return continueForwarding(null, target);
}
// See if the target is a presence we already know of.
let presence;
try {
presence = HandledPromise.unwrap(target);
} catch (e) {
presence = await target;
}
const existingPresenceHandler = presenceToHandler.get(presence);
if (existingPresenceHandler) {
promiseToHandler.set(handledP, existingPresenceHandler);
promiseToPresence.set(handledP, presence);
return continueForwarding(null, handledP);
}
// Remove the mapping, as we don't need a handler.
promiseToHandler.delete(handledP);
return continueForwarding();
} catch (e) {
handledReject(e);
}
return continueForwarding();
};

@@ -382,10 +441,8 @@

handle = (p, operation, ...args) => {
handle = (p, operation, ...opArgs) => {
ensureMaps();
const unfulfilledHandler = promiseToHandler.get(p);
p = shorten(p);
const unsettledHandler = promiseToUnsettledHandler.get(p);
let executor;
if (
unfulfilledHandler &&
typeof unfulfilledHandler[operation] === 'function'
) {
if (unsettledHandler && typeof unsettledHandler[operation] === 'function') {
executor = (resolve, reject) => {

@@ -395,4 +452,7 @@ // We run in a future turn to prevent synchronous attacks,

.then(() =>
// and resolve to the answer from the specific unfulfilled handler,
resolve(unfulfilledHandler[operation](p, ...args)),
// and resolve to the answer from the specific unsettled handler,
// opArgs are something like [prop] or [method, args],
// so we don't risk the user's args leaking into this expansion.
// eslint-disable-next-line no-use-before-define
resolve(unsettledHandler[operation](p, ...opArgs, returnedP)),
)

@@ -413,3 +473,6 @@ .catch(reject);

// and resolve to the forwardingHandler's operation.
resolve(forwardingHandler[operation](o, ...args));
// opArgs are something like [prop] or [method, args],
// so we don't risk the user's args leaking into this expansion.
// eslint-disable-next-line no-use-before-define
resolve(forwardingHandler[operation](o, ...opArgs, returnedP));
})

@@ -420,6 +483,7 @@ .catch(reject);

// We return a handled promise with the default unfulfilled handler.
// We return a handled promise with the default unsettled handler.
// This prevents a race between the above Promise.resolves and
// pipelining.
return new HandledPromise(executor);
const returnedP = new HandledPromise(executor);
return returnedP;
};

@@ -426,0 +490,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc