Skip to content

Commit

Permalink
feat(HandledPromise): add sync unwrap() to get presences
Browse files Browse the repository at this point in the history
Closes #412
  • Loading branch information
michaelfig committed Jan 27, 2020
1 parent d1f25ef commit d755832
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/eventual-send/src/E.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export default function makeE(HandledPromise) {

E.G = makeEGetterProxy;
E.resolve = HandledPromise.resolve;
E.unwrap = HandledPromise.unwrap;

return harden(E);
}
38 changes: 37 additions & 1 deletion packages/eventual-send/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ export function makeHandledPromise(Promise) {
let presenceToHandler;
let presenceToPromise;
let promiseToHandler;
let promiseToPresence; // only for HandledPromise.unwrap
function ensureMaps() {
if (!presenceToHandler) {
presenceToHandler = new WeakMap();
presenceToPromise = new WeakMap();
promiseToHandler = new WeakMap();
promiseToPresence = new WeakMap();
}
}

Expand Down Expand Up @@ -109,6 +111,9 @@ export function makeHandledPromise(Promise) {
// Return undefined.
};
});
// A failed interlock should not be recorded as an unhandled rejection.
// It will bubble up to the HandledPromise itself.
interlockP.catch(_ => {});

const makePostponed = postponedOperation => {
// Just wait until the handler is resolved/rejected.
Expand Down Expand Up @@ -167,6 +172,7 @@ export function makeHandledPromise(Promise) {
// Create table entries for the presence mapped to the
// fulfilledHandler.
presenceToPromise.set(resolvedPresence, handledP);
promiseToPresence.set(handledP, resolvedPresence);
presenceToHandler.set(resolvedPresence, presenceHandler);

// Remove the mapping, as our presenceHandler should be
Expand Down Expand Up @@ -206,10 +212,16 @@ export function makeHandledPromise(Promise) {
}

// See if the target is a presence we already know of.
const presence = await target;
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);
}

Expand Down Expand Up @@ -284,6 +296,30 @@ export function makeHandledPromise(Promise) {
promiseResolve().then(_ => new HandledPromise(executeThen)),
);
},
// TODO verify that this is safe to provide universally, i.e.,
// that by itself it doesn't provide access to mutable state in
// ways that violate normal ocap module purity rules. The claim
// that it does not rests on the handled promise itself being
// necessary to perceive this mutable state. In that sense, we
// can think of the right to perceive it, and of access to the
// target, as being in the handled promise. Note that a .then on
// the handled promise will already provide async access to the
// target, so the only additional authorities are: 1)
// synchronous access for handled promises only, and thus 2) the
// ability to tell, from the client side, whether a promise is
// handled. Or, at least, the ability to tell given that the
// promise is already fulfilled.
unwrap(value) {
ensureMaps();
const pr = presenceToPromise.get(value) || value;
const presence = promiseToPresence.get(pr);
if (!presence) {
throw TypeError(
`Value is not a presence nor a HandledPromise resolved to a presence`,
);
}
return presence;
},
});

defineProperties(HandledPromise, getOwnPropertyDescriptors(staticMethods));
Expand Down
13 changes: 12 additions & 1 deletion packages/eventual-send/test/test-e.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import test from 'tape-promise/tape';
import { E } from '../src/index';
import { E, HandledPromise } from '../src/index';

test('E reexports', async t => {
try {
t.equals(E.resolve, HandledPromise.resolve, 'E reexports resolve');
t.equals(E.unwrap, HandledPromise.unwrap, 'E reexports unwrap');
} catch (e) {
t.isNot(e, e, 'unexpected exception');
} finally {
t.end();
}
});

test('E method calls', async t => {
try {
Expand Down
85 changes: 85 additions & 0 deletions packages/eventual-send/test/test-hp.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,88 @@ test('chained properties', async t => {
t.end();
}
});

test('HandledPromise.unwrap', async t => {
try {
t.throws(
() => HandledPromise.unwrap({}),
TypeError,
`unwrapped non-presence throws`,
);
const p0 = new Promise(_ => {});
t.throws(
() => HandledPromise.unwrap(p0),
TypeError,
`unwrapped unfulfilled Promise throws`,
);
const p1 = new Promise(resolve => {
resolve({});
});
t.throws(
() => HandledPromise.unwrap(p1),
TypeError,
`unwrapped resolved Promise throws`,
);
const p2 = new Promise((_, reject) => {
reject(Error('p2'));
});
// Prevent unhandled promise rejection.
p2.catch(_ => {});
t.throws(
() => HandledPromise.unwrap(p2),
TypeError,
`unwrapped rejected Promise throws`,
);
const hp0 = new HandledPromise(_ => {});
t.throws(
() => HandledPromise.unwrap(hp0),
TypeError,
'unfulfilled HandledPromise throws',
);
const hp1 = new HandledPromise(resolve => {
resolve({});
});
t.throws(
() => HandledPromise.unwrap(hp1),
TypeError,
'resolved HandledPromise throws',
);
const hp2 = new HandledPromise((_, reject) => {
reject(Error('hp2'));
});
// Prevent unhandled promise rejection.
hp2.catch(_ => {});
t.throws(
() => HandledPromise.unwrap(hp2),
TypeError,
'rejected HandledPromise throws',
);
let presence;
const hp3 = new HandledPromise((_res, _rej, resolveWithPresence) => {
presence = resolveWithPresence({});
});
t.equals(typeof presence, 'object', `typeof presence is object`);
t.equals(
HandledPromise.unwrap(hp3),
presence,
`unwrapped HandledPromise is presence`,
);
t.equals(
HandledPromise.unwrap(presence),
presence,
`unwrapped presence is presence`,
);
const hp4 = new HandledPromise(resolve => {
resolve(hp3);
});
t.equals(
HandledPromise.unwrap(hp4),
presence,
`unwrapped forwarded HandledPromise is presence`,
);
} catch (e) {
t.isNot(e, e, 'unexpected exception');
} finally {
t.end();
}
});

0 comments on commit d755832

Please sign in to comment.