Oriol
Oriol

Reputation: 288290

Can't set "apply" trap to Proxy object

I created a Proxy object with an "apply" trap:

var target = {},
    handler = { apply: () => 42 }
    proxy = new Proxy(target, handler);
proxy(); // TypeError: proxy is not a function

Therefore, the Proxy object should be callable. However, it doesn't work.

Why?

Upvotes: 15

Views: 4189

Answers (3)

Thomas
Thomas

Reputation: 661

Using the info of the other answers I quickly cobbled together a generic utility.
You may add the call functionality to any existing object (not just functions) using the following utility makeCallable.

type WrappedTarget<T extends object> = {
  (...args: any[]): any;
  originalTarget: T;
};

function createProxyHandlerForCallableAroundWrappedTarget<T extends object>(): ProxyHandler<WrappedTarget<T>> {
  return {
    apply(target: WrappedTarget<T>, thisArg: any, argArray: any[]): any {
      return Reflect.apply(target, thisArg, argArray);
    },
    get(target: WrappedTarget<T>, p: string | symbol, receiver: any): any {
      if (p !== 'apply') {
        return Reflect.get(target.originalTarget, p, receiver);
      } else {
        return Reflect.get(target, p, receiver);
      }
    },
    set(target: WrappedTarget<T>, p: string | symbol, value: any, receiver: any): boolean {
      return Reflect.set(target.originalTarget, p, value, receiver);
    },
    construct(target: WrappedTarget<T>, argArray: any[], newTarget: any): object {
      return Reflect.construct(target.originalTarget as any, argArray, newTarget);
    },
    has(target: WrappedTarget<T>, p: string | symbol): boolean {
      return Reflect.has(target.originalTarget, p);
    },
    setPrototypeOf(target: WrappedTarget<T>, v: object | null): boolean {
      return Reflect.setPrototypeOf(target.originalTarget, v);
    },
    deleteProperty(target: WrappedTarget<T>, p: string | symbol): boolean {
      return Reflect.deleteProperty(target.originalTarget, p);
    },
    defineProperty(target: WrappedTarget<T>, property: string | symbol, attributes: PropertyDescriptor): boolean {
      return Reflect.defineProperty(target.originalTarget, property, attributes);
    },
    getPrototypeOf(target: WrappedTarget<T>): object | null {
      return Reflect.getPrototypeOf(target.originalTarget);
    },
    ownKeys(target: WrappedTarget<T>): ArrayLike<string | symbol> {
      return Reflect.ownKeys(target.originalTarget);
    },
    isExtensible(target: WrappedTarget<T>): boolean {
      return Reflect.isExtensible(target.originalTarget);
    },
    preventExtensions(target: WrappedTarget<T>): boolean {
      return Reflect.preventExtensions(target.originalTarget);
    },
    getOwnPropertyDescriptor(target: WrappedTarget<T>, p: string | symbol): PropertyDescriptor | undefined {
      return Reflect.getOwnPropertyDescriptor(target.originalTarget, p);
    },
  };
}

export function makeCallable<T extends object, ApplyFn extends (this: T, ...args: any[]) => any>(
  target: T,
  onApply: ApplyFn
): T & {
  (...args: Parameters<ApplyFn>): ReturnType<ApplyFn>;
} {
  const wrappedTarget = function (...args: any[]) {
    return Reflect.apply(onApply, target, args);
  };
  (wrappedTarget as any).originalTarget = target;
  return new Proxy(wrappedTarget, createProxyHandlerForCallableAroundWrappedTarget()) as any;
}

Upvotes: 0

user0103
user0103

Reputation: 1272

I came here from Google searching for 'callable proxy'.
While the existing answer works, there is another alternative that may be 'cleaner' for some purposes:

class Callable extends Function {
  constructor() {
    super()    
    return new Proxy(this, {
      apply: (target, thisArg, args) => target._call(...args)
    })
  }
  
  _call(...args) {
    console.log(this, args)
  }
}

You can define all traps in proxy as usual.
All credits to arccoza and his gist here.

Upvotes: 1

Oriol
Oriol

Reputation: 288290

According to the definition of the [[Call]] internal method of Proxy objects it should work:

However, there is a problem: not all Proxy objects have the [[Call]] method:

A Proxy exotic object only has a [[Call]] internal method if the initial value of its [[ProxyTarget]] internal slot is an object that has a [[Call]] internal method.

Therefore, the target must be a function object:

var target = () => {},
    handler = { apply: () => 42 }
    proxy = new Proxy(target, handler);
proxy(); // 42

Note that I defined target using an arrow function in order to create a function object which is not a constructor function. This way the Proxy object can be called but not instantiated.

If you want to add a "construct" trap too, the target must have a [[Construct]] method, so define it with a function declaration or function expression.

Upvotes: 26

Related Questions