Reputation: 288290
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
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
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
Reputation: 288290
According to the definition of the [[Call]] internal method of Proxy objects it should work:
- Let trap be GetMethod(handler,
"apply"
).- Return Call(trap, handler, «target, thisArgument, CreateArrayFromList(argumentsList)»).
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