Skip to content

Commit 5ec5b78

Browse files
committed
feat(HandledPromise): add sync unwrap() to get presences
Closes #412
1 parent d1f25ef commit 5ec5b78

File tree

4 files changed

+135
-2
lines changed

4 files changed

+135
-2
lines changed

packages/eventual-send/src/E.js

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export default function makeE(HandledPromise) {
6868

6969
E.G = makeEGetterProxy;
7070
E.resolve = HandledPromise.resolve;
71+
E.unwrap = HandledPromise.unwrap;
7172

7273
return harden(E);
7374
}

packages/eventual-send/src/index.js

+37-1
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,13 @@ export function makeHandledPromise(Promise) {
5353
let presenceToHandler;
5454
let presenceToPromise;
5555
let promiseToHandler;
56+
let promiseToPresence; // only for HandledPromise.unwrap
5657
function ensureMaps() {
5758
if (!presenceToHandler) {
5859
presenceToHandler = new WeakMap();
5960
presenceToPromise = new WeakMap();
6061
promiseToHandler = new WeakMap();
62+
promiseToPresence = new WeakMap();
6163
}
6264
}
6365

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

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

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

208214
// See if the target is a presence we already know of.
209-
const presence = await target;
215+
let presence;
216+
try {
217+
presence = HandledPromise.unwrap(target);
218+
} catch (e) {
219+
presence = await target;
220+
}
210221
const existingPresenceHandler = presenceToHandler.get(presence);
211222
if (existingPresenceHandler) {
212223
promiseToHandler.set(handledP, existingPresenceHandler);
224+
promiseToPresence.set(handledP, presence);
213225
return continueForwarding(null, handledP);
214226
}
215227

@@ -284,6 +296,30 @@ export function makeHandledPromise(Promise) {
284296
promiseResolve().then(_ => new HandledPromise(executeThen)),
285297
);
286298
},
299+
// TODO verify that this is safe to provide universally, i.e.,
300+
// that by itself it doesn't provide access to mutable state in
301+
// ways that violate normal ocap module purity rules. The claim
302+
// that it does not rests on the handled promise itself being
303+
// necessary to perceive this mutable state. In that sense, we
304+
// can think of the right to perceive it, and of access to the
305+
// target, as being in the handled promise. Note that a .then on
306+
// the handled promise will already provide async access to the
307+
// target, so the only additional authorities are: 1)
308+
// synchronous access for handled promises only, and thus 2) the
309+
// ability to tell, from the client side, whether a promise is
310+
// handled. Or, at least, the ability to tell given that the
311+
// promise is already fulfilled.
312+
unwrap(value) {
313+
ensureMaps();
314+
const pr = presenceToPromise.get(value) || value;
315+
const presence = promiseToPresence.get(pr);
316+
if (!presence) {
317+
throw TypeError(
318+
`Value is not a presence nor a HandledPromise resolved to a presence`,
319+
);
320+
}
321+
return presence;
322+
},
287323
});
288324

289325
defineProperties(HandledPromise, getOwnPropertyDescriptors(staticMethods));

packages/eventual-send/test/test-e.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
import test from 'tape-promise/tape';
2-
import { E } from '../src/index';
2+
import { E, HandledPromise } from '../src/index';
3+
4+
test('E reexports', async t => {
5+
try {
6+
t.equals(E.resolve, HandledPromise.resolve, 'E reexports resolve');
7+
t.equals(E.unwrap, HandledPromise.unwrap, 'E reexports unwrap');
8+
} catch (e) {
9+
t.isNot(e, e, 'unexpected exception');
10+
} finally {
11+
t.end();
12+
}
13+
});
314

415
test('E method calls', async t => {
516
try {

packages/eventual-send/test/test-hp.js

+85
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,88 @@ test('chained properties', async t => {
4545
t.end();
4646
}
4747
});
48+
49+
test('HandledPromise.unwrap', async t => {
50+
try {
51+
t.throws(
52+
() => HandledPromise.unwrap({}),
53+
TypeError,
54+
`unwrapped non-presence throws`,
55+
);
56+
const p0 = new Promise(_ => {});
57+
t.throws(
58+
() => HandledPromise.unwrap(p0),
59+
TypeError,
60+
`unwrapped unfulfilled Promise throws`,
61+
);
62+
const p1 = new Promise(resolve => {
63+
resolve({});
64+
});
65+
t.throws(
66+
() => HandledPromise.unwrap(p1),
67+
TypeError,
68+
`unwrapped resolved Promise throws`,
69+
);
70+
const p2 = new Promise((_, reject) => {
71+
reject(Error('p2'));
72+
});
73+
// Prevent unhandled promise rejection.
74+
p2.catch(_ => {});
75+
t.throws(
76+
() => HandledPromise.unwrap(p2),
77+
TypeError,
78+
`unwrapped rejected Promise throws`,
79+
);
80+
const hp0 = new HandledPromise(_ => {});
81+
t.throws(
82+
() => HandledPromise.unwrap(hp0),
83+
TypeError,
84+
'unfulfilled HandledPromise throws',
85+
);
86+
const hp1 = new HandledPromise(resolve => {
87+
resolve({});
88+
});
89+
t.throws(
90+
() => HandledPromise.unwrap(hp1),
91+
TypeError,
92+
'resolved HandledPromise throws',
93+
);
94+
const hp2 = new HandledPromise((_, reject) => {
95+
reject(Error('hp2'));
96+
});
97+
// Prevent unhandled promise rejection.
98+
hp2.catch(_ => {});
99+
t.throws(
100+
() => HandledPromise.unwrap(hp2),
101+
TypeError,
102+
'rejected HandledPromise throws',
103+
);
104+
let presence;
105+
const hp3 = new HandledPromise((_res, _rej, resolveWithPresence) => {
106+
presence = resolveWithPresence({});
107+
});
108+
t.equals(typeof presence, 'object', `typeof presence is object`);
109+
t.equals(
110+
HandledPromise.unwrap(hp3),
111+
presence,
112+
`unwrapped HandledPromise is presence`,
113+
);
114+
t.equals(
115+
HandledPromise.unwrap(presence),
116+
presence,
117+
`unwrapped presence is presence`,
118+
);
119+
const hp4 = new HandledPromise(resolve => {
120+
resolve(hp3);
121+
});
122+
t.equals(
123+
HandledPromise.unwrap(hp4),
124+
presence,
125+
`unwrapped forwarded HandledPromise is presence`,
126+
);
127+
} catch (e) {
128+
t.isNot(e, e, 'unexpected exception');
129+
} finally {
130+
t.end();
131+
}
132+
});

0 commit comments

Comments
 (0)