xenoterracide
xenoterracide

Reputation: 16865

How does using a Proxy in js making a Promise look synchronous actually work?

    const handler: ProxyHandler<any> = {
      get: (target: Promise<any>, prop: string, receiver: any) => {
        return target.then((o) => {
          return o[prop].apply(o);
        });
      },
    };
    return new Proxy(obj, handler);

So I have this code that I copied from a gist on the internet, and it seems to work. I understand how the trap works, but I don't get how the proxy makes a return of a promise act like a synchronous value. My colleague is afraid this code has a race condition. Are there any race conditions? what's actually happening under the hood? do I need to improve this code any to make it safe?

code is written in typescript and running on nodejs 10.

Upvotes: 2

Views: 692

Answers (2)

ProdigySim
ProdigySim

Reputation: 2933

It doesn't look like this code makes access synchronous at all. It looks like it serves to make any methods on the promise payload available on the promise, but they will still have deferred execution when invoked.

For example, given the following API:

interface Bird {
  speak(): void;
}
function getBird(): Promise<Bird> {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ speak() { console.log('CAW'); } }, 1000);
  });
}

function proxyPromise<T>(promise: Promise<T>) {
  // Your promise proxying code here
}

The proxyPromise code would allow you to call methods on the proxied object, and defer them until the promise is resolved:

const proxiedBird = proxyPromise(getBird());
console.log("Fetching Bird")
proxiedBird.speak;
console.log("Told it to speak");

This code would execute fine, but it won't make the speak() operation run synchronously--it will still wait for the getBird() promise to resolve. So in output you would see:

Fetching Bird
Told it to Speak
CAW

The code snippet you have would do nothing to support fields or methods with parameters on the proxied object.

You could describe its safe behavior with some typescript typing:

type MethodProxy<T> = {
  [idx in keyof T]: T[idx] extends () => infer U ? Promise<U> : never;
}

function proxyPromise<T>(promise: Promise<T>): MethodProxy<T> {
  const handler: ProxyHandler<any> = {
    get: (target: Promise<any>, prop: string, receiver: any) => {
      return target.then((o) => {
        return o[prop].apply(o);
      });
    },
  };
  return new Proxy(promise, handler);
}

Upvotes: 1

Pedro Mutter
Pedro Mutter

Reputation: 1224

Tricky question @xenoterracide. But answering you, actually, this doesn't work synchronously at all, basically, you turned every obj property to be accessed only asynchronously, and if the property is not a function, it throws an error.

Under the hood, you are only trapping a promise get properties, and in the trap, waiting for the promise to resolve and execute the property (function) in this value resolved by the promise.

I simulate it in this playground:

const handler: ProxyHandler<any> = {
    get: (target: Promise<any>, prop: string, receiver: any) => {
    return target.then((o) => {
        return o[prop].apply(o);
    });
    },
};

const obj = {
    a() {
        return 'Hi'
    },
    b: 5
}

const proxy = new Proxy(Promise.resolve(obj), handler);

//both prints promises
console.log(proxy.a) // after resolved, return 'Hi'
console.log(proxy.b) // error

This proxy approach could be useful if you don't want to resolve the promise itself. But you would need to await every property, and take care of not function ones.

Upvotes: 0

Related Questions