First step in figuring out #5. The use case in question: postMessaging a promise from window A (where it was created) to window B.
Promises are tricky because they not only have internal state to encapsulate (see their internal slots), but also that internal state can be manipulated by their creator in window A.
For maps and sets, their internal state can also be manipulated, but once the clone happens, they are disconnected: window A and window B end up with completely different maps. Is this right for promises? There are a few reasons to think not:
- Promises represent an async operation; maps represent a mutable data structure. If you clone a map and get a snapshot, you still have a mutable data structure on the other side; its "essence" has been maintained. But if you clone a pending promise and it then stays pending forever (i.e. is snapshotted), then you no longer have something representing that async operation on the other side: its "essence" has been lost.
- Promises are inherently asynchronous, so it seems much more possible to maintain the link between the original and the clone.
Thus, I think the following would work:
-
Promise B gets initialized with the same [[PromiseState]] and [[PromiseResult]] as promise A. If [[PromiseState]] is "fulfilled" or "rejected", then there's nothing to do. (If [[PromiseState]] is undefined, then someone tried to clone the promise before it was initialized, and we should throw.)
-
The implementation internally adds something like:
promiseA.then(v => resolvePromiseBWith(v), r => rejectPromiseBWith(r))
As long as the implementation uses the original value of Promise.prototype.then, this would be unobservable in window A, which is good.
-
The resolvePromiseBWith and rejectPromiseBWith meta-functions would go through the public API in the same way as all of the promise spec currently does. More formally, in step 1 we would save the resolve and reject functions passed to us by the constructor, and resolvePromiseBWith(v) would essentially be something like "post a message to window B to call the resolve function it saved with value v"
-
If v or r are not structured-clonable, instead the implementation would post a message to window B to call the reject function it saved with an informative error. I guess we'd need to take this into account during step 1 as well.
Still to think about: how to match up constructors across realms? E.g. if someone subclasses Promise to CancelablePromise, how do we ensure that a clone of a cancelable promise from window A becomes a cancelable promise in window B? Should it even be such a cancelable promise? This seems to be a larger issue related to much of the things this repo is generally concerned with. For a first pass, I'd be happy with just reconstituting as normal Promise instances, as long as it's future-compatible. (E.g. in the future subclasses could have a "make clone data" and "initialize with clone data" hook that gets called if they are present?)
First step in figuring out #5. The use case in question: postMessaging a promise from window A (where it was created) to window B.
Promises are tricky because they not only have internal state to encapsulate (see their internal slots), but also that internal state can be manipulated by their creator in window A.
For maps and sets, their internal state can also be manipulated, but once the clone happens, they are disconnected: window A and window B end up with completely different maps. Is this right for promises? There are a few reasons to think not:
Thus, I think the following would work:
Promise B gets initialized with the same [[PromiseState]] and [[PromiseResult]] as promise A. If [[PromiseState]] is
"fulfilled"or"rejected", then there's nothing to do. (If [[PromiseState]] is undefined, then someone tried to clone the promise before it was initialized, and we should throw.)The implementation internally adds something like:
As long as the implementation uses the original value of
Promise.prototype.then, this would be unobservable in window A, which is good.The
resolvePromiseBWithandrejectPromiseBWithmeta-functions would go through the public API in the same way as all of the promise spec currently does. More formally, in step 1 we would save theresolveandrejectfunctions passed to us by the constructor, andresolvePromiseBWith(v)would essentially be something like "post a message to window B to call theresolvefunction it saved with valuev"If
vorrare not structured-clonable, instead the implementation would post a message to window B to call therejectfunction it saved with an informative error. I guess we'd need to take this into account during step 1 as well.Still to think about: how to match up constructors across realms? E.g. if someone subclasses
PromisetoCancelablePromise, how do we ensure that a clone of a cancelable promise from window A becomes a cancelable promise in window B? Should it even be such a cancelable promise? This seems to be a larger issue related to much of the things this repo is generally concerned with. For a first pass, I'd be happy with just reconstituting as normalPromiseinstances, as long as it's future-compatible. (E.g. in the future subclasses could have a "make clone data" and "initialize with clone data" hook that gets called if they are present?)