Promises vs Proxies

Hi,

I'm puzzled by the interaction between Promises and Proxy objects.
I'm trying the run the following code:

const prom = new Promise( (res, rej) => res( 34 ) );
const xprom = new Proxy( prom, {} );

Promise.prototype.then.call( xprom, val => console.log( val ) );

It fails with the error message:

proxy.js:5
Promise.prototype.then.call( xprom, val => console.log( val ) );
                       ^

TypeError: Method Promise.prototype.then called on incompatible receiver [object Object]
    at Proxy.then (<anonymous>)
    at Object.<anonymous> (proxy.js:5:24)
    at Module._compile (node:internal/modules/cjs/loader:1108:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)
    at Module.load (node:internal/modules/cjs/loader:973:32)
    at Function.Module._load (node:internal/modules/cjs/loader:813:14)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)
    at node:internal/main/run_main_module:17:47

If I understand correctly, this is conform to the ECMASpefication that
defines Promise.prototype.then as:

27.2.5.4 Promise.prototype.then ( onFulfilled, onRejected )

When the then method is called with arguments onFulfilled and onRejected, the following steps are taken:

    1. Let promise be the this value.
    2. If IsPromise(promise) is false, throw a TypeError exception.

and isPromise is defined as :

27.2.1.6 IsPromise ( x )

The abstract operation IsPromise takes argument x. It checks for the promise brand on an object. It performs the following steps when called:

    1. If Type(x) is not Object, return false.
    2. If x does not have a [[PromiseState]] internal slot, return false.
    3. Return true.

I'm not quite sure about the [[PromiseState]] but my guess is that
this designates only "builtin" Promise objects.

The problem I see with 27.2.1.6, is that it creates a means a program
may use to distinguish plain values from proxies values. I though that
the whole Proxy design was intended to make this impossible. My
question is then the following:

Is there a reason for which Promise.prototype.then (and all the
other Promise.protype methods) must behave differently for promises and
proxy objects or could Promise.prototype.then be modified so that it is
also able to handle proxy objects?

The reason for that question is the following. We are trying to build
a tool for debugging Typecript programs. Our idea is to map TypeScript
types to higher-order contracts and to execute them with dynamic
verification. These higher-order contracts are implemented with
proxies. So far, we are able to cope with all the TypeScript
constructs but promises because of the aformentionned problem.

I thank you in advance for your help or explanations.

Proxies are not designed to be 'invisible'. You can see the same thing with Set

const s = new Set(['a']);
s.has('a'); // true
Set.prototype.has.call(s, 'a'); // true

const p = new Proxy(s, {});
p.has('a'); // TypeError!
Set.prototype.has.call(p, 'a'); // TypeError!

Every builtin except Error and Proxy has a robust way to identity it, usually via a prototype method that throws. Internal slots and private fields don’t “tunnel” through a Proxy, so a proxy for any builtin with slots (or any object that exposes an identity-based detection mechanism) is thus trivially detectable as such.

Thank you for these explanations. I realize that I was wrong assuming the "invisibility" of proxy objects.
However, to get back to my question, Is there a reason for which Promise.then should not accept
proxys (or is it just because it's one method among many others)?

Because a Proxy doesn’t have the internal slots of a Promise.

A Proxy can certainly be a thenable, but Promise.prototype.then can’t be used unless the receiver is a proper Promise.

To add to what @ljharb has already said.

For Promise.prototype.then or any method that references internal/private state to work on a proxy version of the object there would need to be an api like Proxy.getOriginalObject(proxy), and this is the bit that would defeat the design of Proxy.

I imagine that this should be doable using a new proxy handler method but I think I understand now why
it would be bad idea to add a proxy support directly in "Promise.prototype.then". Thanks for your time and your explanations.

1 Like