invokable
v1.0.3
Published
a way to make invokable JavaScript objects
Downloads
65
Maintainers
Readme
invokable
A way to make invokable JavaScript objects. Reminiscent of Python's __call__
.
Installation
npm install invokable
Usage
Using with a class:
import {Invokable} from 'invokable';
class Rect {
constructor(width, height) {
this.width = width;
this.height = height;
return Invokable.create(this);
}
get area() {
return this.width * this.height;
}
[Invokable.call](depth = 1) {
return this.area * depth;
}
}
- Implement a method with the computed name
[Invokable.call]
with any signature of your choice. - End the constructor with
return Invokable.create(this)
Also works with a plain object:
import {Invokable} from 'invokable';
const Rect = (width, height) =>
Invokable.create({
width,
height,
get area() {
return this.width * this.height;
},
[Invokable.call](depth = 1) {
return this.area * depth;
},
});
- Declare a property with the computed name
[Invokable.call]
with any function of your choice. - Pass the entire object to
Invokable.create
Capabilities and Limitations
Invokable.create
can:
- Preserve the
this
context of the object. - Do the right thing when an object's
[Invokable.call]
method has been replaced at runtime. - Replicate all properties without triggering getters and setters.
- Inherit the same prototype, ensuring things like
constructor
and theinstanceof
operator still work. - Create functions whose
name
property is writable. Simply define aname
property in the original target object.
It cannot:
- Modify the original object. A new object that masquerades as the original is returned.
- Do anything with an object that does not implement
[Invokable.call]
. ATypeError
is thrown when such an object is given. - Work on JavaScript engines that don't support
Object.setPrototypeOf
,Object.getPrototypeOf
,Object.getOwnPropertyDescriptors
, andObject.defineProperties
, unless they have been polyfilled. - Be rebound using
.bind(...)
. However, the[Invokable.call]
method can still be rebound.
API
Invokable.create(target)
takes atarget
object that conforms to theInvokable
interface and returns a new object that is a function that masquerades as the originaltarget
object. All properties, values, getters, setters, or otherwise are replicated, including the prototype.Normal function objects have a read-only property called
name
. However, if thetarget
object defines its own value or getter calledname
, then thename
property will be made writable in the result. In all cases, thename
property will default to the original function or surrounding class name.If the
target
object does not implement[Invokable.call]
, aTypeError
is thrown.Invokable.call
is a tag that denotes callability. It is a constant used by an object to conform to theInvokable
interface. Its current value is the string__call__
, but it may become a symbol in the future.
TypeScript Support
TypeScript support comes out of the box without any additional setup. Here is the TypeScript version of the class example:
import {Invokable} from 'invokable';
class Rect {
constructor(public width: number, public height: number) {
return Invokable.create(this);
}
get area() {
return this.width * this.height;
}
[Invokable.call](depth = 1) {
return this.area * depth;
}
}
interface Rect {
(depth?: number): number;
}
Note that it is necessary to modify the interface generated by the class in order make class instances callable to the type system.
The TypeScript version of the plain object example:
import {Invokable} from 'invokable';
const Rect = (width: number, height: number) =>
Invokable.create({
width,
height,
get area() {
return this.width * this.height;
},
[Invokable.call](depth = 1) {
return this.area * depth;
},
});
Performance
The following observations are based on the benchmarks listed further below.
- Creating an invokable object is very slow. Doing so for a plain object incurs
around a 7x slowdown, whereas doing so for a class instance can suffer from
around a 700x slowdown. Therefore, avoid performing a huge number of calls to
Invokable.create
in a hot code path. - Property access, regardless of whether or not through the prototype, is not significantly impacted.
- Invoking the invokable object itself is also not guaranteed to be faster than directly invoking the method it points to.
Inspiration
invokable
is inspired by the
callable-object
project.
Benchmarks
To run these on your machine, run npm run bench
. The process typically takes
a few minutes, and directly writes results to STDOUT as they finish running.
Results were measured on an Intel Core M @ 1.2GHz with 8GB of DDR3-1600 on Node.JS v8.3.0. Throughput numbers are expressed in operations per second (ops).
Class instantiation, no args
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | -----: | --------------: | | plain | 107464004 | ±1.71% | 100.00% | | invokable | 147857 | ±3.32% | 0.14% |
Class instantiation, 5 args
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | -----: | --------------: | | plain | 12722162 | ±1.78% | 100.00% | | invokable | 144240 | ±3.45% | 1.13% |
Class instantiation, 10 args
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | -----: | --------------: | | plain | 9185841 | ±1.48% | 100.00% | | invokable | 144790 | ±3.58% | 1.58% |
Class instance: own property access
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | -----: | --------------: | | plain | 450120750 | ±1.48% | 100.00% | | invokable | 448748906 | ±1.51% | 99.70% |
Class instance: method call on prototype
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | -----: | --------------: | | plain | 443185849 | ±1.68% | 98.26% | | invokable | 451041317 | ±1.64% | 100.00% |
Class instance: getter on prototype
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | -----: | --------------: | | plain | 448271681 | ±2.17% | 100.28% | | invokable | 447015599 | ±1.31% | 100.00% |
Class instance: setter on prototype
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | -----: | --------------: | | plain | 356063076 | ±3.93% | 96.11% | | invokable | 370482861 | ±1.55% | 100.00% |
Object creation, no args
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | -----: | --------------: | | plain | 495464 | ±5.21% | 100.00% | | invokable | 73149 | ±3.58% | 14.76% |
Object creation, 5 args
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | -----: | --------------: | | plain | 455371 | ±8.85% | 100.00% | | invokable | 71283 | ±3.99% | 15.65% |
Object creation, 10 args
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | -----: | --------------: | | plain | 470126 | ±6.42% | 100.00% | | invokable | 70540 | ±4.37% | 15.00% |
Object: own property access
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | -----: | --------------: | | plain | 416151276 | ±0.96% | 99.76% | | invokable | 417164274 | ±0.87% | 100.00% |
Object: own property call
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | -----: | --------------: | | plain | 83609556 | ±2.94% | 100.00% | | invokable | 54168693 | ±1.27% | 64.79% |
Object: own property getter
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | -----: | --------------: | | plain | 51863522 | ±1.21% | 100.00% | | invokable | 51401432 | ±5.03% | 99.11% |
Object: own property setter
| Test description | Throughput | Error | Percent of best | | ---------------- | ---------: | ------: | --------------: | | plain | 267193097 | ±0.85% | 100.00% | | invokable | 197801204 | ±18.43% | 74.03% |
Invocation, no args
| Test description | Throughput | Error | Percent of best |
| ---------------------------- | ---------: | -----: | --------------: |
| instance.__call__()
| 17807793 | ±1.19% | 95.57% |
| instance[Invokable.call]()
| 17446234 | ±1.55% | 93.63% |
| instance()
| 18633571 | ±1.51% | 100.00% |
Invocation, 5 args
| Test description | Throughput | Error | Percent of best |
| ---------------------------- | ---------: | -----: | --------------: |
| instance.__call__()
| 8164700 | ±3.61% | 97.58% |
| instance[Invokable.call]()
| 8367181 | ±1.47% | 100.00% |
| instance()
| 7318022 | ±5.51% | 87.46% |
Invocation, 10 args
| Test description | Throughput | Error | Percent of best |
| ---------------------------- | ---------: | -----: | --------------: |
| instance.__call__()
| 6898069 | ±2.00% | 94.93% |
| instance[Invokable.call]()
| 7040076 | ±3.05% | 96.88% |
| instance()
| 7266453 | ±2.24% | 100.00% |