I have this simple class:
class Foo {
constructor() {
this.init();
return this;
}
init() {
this.onInit();
}
onInit(callback) {
this.onInit = () => callback();
return this;
}
}
new Foo().onInit(() => console.log('baz'));
It's obviously flawed, because it will call init before the onInit method is able to define the onInit property/callback.
How can I make this work without change the interface?
How can I make this work without change the interface?
You can't, the interface is inherently flawed. That's really the answer to your question.
Continuing, though, with "what can I do instead":
If you need to have a callback called during initialization, you need to pass it to the constructor, not separately to the onInit method.
class Foo {
constructor(callback) {
this.onInit = () => {
callback(); // Call the callback
return this; // Chaining seemed important in your code, so...
};
// Note: Constructors don't return anything
}
}
new Foo(() => console.log('baz'));
In a comment you've said:
I see your point, the fact is that my library is new Something().onCreate().onUpdate()
It sounds like you might want to adopt the builder pattern instead:
class Foo {
constructor(callbacks) {
// ...use the callbacks here...
}
// ...
}
Foo.Builder = class {
constructor() {
this.callbacks = {};
}
onCreate(callback) {
this.callbacks.onCreate = callback;
}
onUpdate(callback) {
this.callbacks.onUpdate = callback;
}
// ...
build() {
// Validity checks here, do we have all necessary callbacks?
// Then:
return new Foo(this.callbacks);
}
};
let f = new Foo.Builder().onCreate(() => { /*...*/}).onUpdate(() => { /*... */}).build();
...although to be fair, a lot of the advantages (though not all) of the builder pattern can be realized in JavaScript by just passing an object into constructor directly and doing your validation there, e.g.:
let f = new Foo({
onCreate: () => { /*...*/},
onUpdate: () => { /*...*/}
});
Assuming that onInit is supposed to be some sort of hook to be called synchronously whenever an object is instantiated, you can't solve this on the instance level.
You can make onInit a static function, like so:
class Foo {
constructor() {
// whatever
Foo.onInit();
}
static onInit() {} // empty default
}
Foo.onInit = () => console.log('baz'); // Override default with your own function
const f = new Foo();
const f2 = new Foo();
Related
I have a class in which i use strategy pattern it looks something like this:
class Foo {
constructor() {
this.doStuff = function() { /* not used really */ }
}
create(config) {
this.type = config.type;
// assign other variables to this
this.doStuff = StuffStrategy(config.type);
this.doStuff();
}
}
StuffStrategy is a function that returns other functions which use this context differently based on type.
function StuffStrategy(type) {
switch(type) {
case A: return StrategyA;
case B: return StrategyB;
// ...
}
}
function StrategyA() {
if(this.someVarInFoo) {
return 'a thing'
} else
return 'a different thing' + this.otherVarInFoo
}
I assign particular Strategy function inside create method.
Then I would like to test the create method if it calls doStuff.
describe('how create method works', () => {
const instance = new Foo();
const spy = jest.spyOn(instance, 'doStuff');
instance.create(config);
expect(spy).toBeCalled();
});
But when I try to make spy before calling instance.create then it refers to default method assigned in constructor, which gets replaced inside create.
If i make spy after calling instance.create then it will not pick the call
.
I tried to add .bind when defining this.doStuff:
this.doStuff = StuffStrategy(config.type).bind(this);
but it does not work either.
Is there something wrong with my setup?
How can I make this test case work?
You have to spyOn the strategy methods of your Foo class. So for every config.type you check then which strategy method has been called.
export class Foo {
constructor(){
this.doStuff = null;
}
create(config){
this.type = config.type;
// assign other variables to this
this.doStuff = StuffStrategy(config.type);
this.doStuff();
}
strategyA(){...}
strategyB(){...}
StuffStrategy(configtype) {
switch (configtype) {
case "A": return this.strategyA;
case "B": return this.strategyB;
}
}
}
import { Foo } from 'anyPlaceFoo/foo';
describe('Strategy', () => {
it('should call strategy A', () => {
const foo = new Foo();
// here you can spy on every strategy method.
jest.spyOn(foo, 'strategyA');
jest.spyOn(foo, 'strategyB');
foo.create({ type: 'A' });
// check if the selected one has been called but not the others
expect(foo.strategyA).toHaveBeenCalled();
expect(foo.strategyB).not.toHaveBeenCalled();
})
})
I have two classes, A and B. What I am trying to do is to pass data from A to B after receiving a message from sockets.
This is simplified look of how classes are defined:
class A:
export default class A {
client;
callbacks;
constructor() {
this.callbacks = {
open: () => this.client.logger.debug('open'),
close: () => this.client.logger.debug('closed'),
message: (data) => {this.client.logger.log(data)}, //I want to pass this data object to class B
};
this.client = new Spot(constants.apiKey, constants.apiSecret, {
baseURL: constants.baseURL,
wsURL: constants.wsURL,
});
this.client.userData(listenKey, this.callbacks);
}
}
I already have a property of A in class definition of B:
export default class B {
account;
constructor() {
this.account = new A();
}
}
What would be a correct/standard way to connect these two so I get a 'data' object from class A every time the socket message callback from class A is triggered?
I am a bit new with JS, but on iOS we would use a delegation pattern, with a protocol, that says:
class A will have a delegate property.
A delegate (class B) must implement a protocol (in this case it would be a requirement to implement method called didReceiveMessage(data).
After that, when a message is received in class A, we would just do(in socket message callback shown above) something like this.delegate.didReceiveMessage(data).
Protocol usage here is not important generally, but it is a plus, cause from A class, we can only access didReceiveData(data) method trough a delegate property, and nothing else (other properties / methods of class B are not visible). At least that is how it works in Swift/Obj-C. I just mentioned it, cause I am curious is this how it is done in JS too.
I guess there is some similar mechanism in Javascript, or some more standard/better way to achieve this kind of data sending between objects?
on iOS we would use a delegation pattern, with a protocol
You can do it exactly as you described:
export default class A {
client;
delegate;
constructor(delegate) {
this.delegate = delegate;
this.client = new Spot(constants.apiKey, constants.apiSecret, {
baseURL: constants.baseURL,
wsURL: constants.wsURL,
});
const callbacks = {
open: () => this.client.logger.debug('open'),
close: () => this.client.logger.debug('closed'),
message: (data) => this.delegate.didReceiveMessage(data),
};
this.client.userData(listenKey, callbacks);
}
}
export default class B {
account;
constructor() {
this.account = new A(this);
}
didReceiveMessage(data) {
console.log(data); // or whatever
}
}
There is no interface (protocol) declaration that would tell A which properties and methods it may access on the passed delegate, but the contract exists of course. You should document it in prose. (Or use TypeScript).
Notice also how your class A interacts with the Spot client, it uses very much the same pattern of passing an object with event handler methods.
A simpler pattern in JavaScript, if you just need a single method in your protocol, is to pass a callable function only:
export default class A {
client;
constructor(onMessage) {
this.client = new Spot(constants.apiKey, constants.apiSecret, {
baseURL: constants.baseURL,
wsURL: constants.wsURL,
});
this.client.userData(listenKey, {
open: () => this.client.logger.debug('open'),
close: () => this.client.logger.debug('closed'),
message: onMessage,
});
}
}
export default class B {
account;
constructor() {
this.account = new A(this.didReceiveMessage.bind(this));
// or inline:
this.account = new A(data => {
console.log(data); // or whatever
});
}
didReceiveMessage(data) {
console.log(data); // or whatever
}
}
I am not an expert on NodeJs, but you can use something like an emitter plugin.
In javascript, it would look like this:
function A() {
Emitter(this);
this.action = function() {
console.log("something happened");
this.emit("action", { prop: "value" });
};
}
function B(a_instance) {
// subscribe to "action" event
a.on("action", function(data) {
console.log(data.prop); // "value"
});
};
var myA = new A();
var myB = new B(myA);
myA.action();
I would like to convert a function that return multiple functions, into a class with a constructor that should be called only with the new keyword.
I tried this :
const toast = () => {
return ({
getToast: () => {
return 'toast'
},
setToast: () => {
return 'wtf'
}
})
}
class t {
constructor() {}
}
const t1 = t.bind(toast())
const tt = new t1()
console.log(tt.getToast)
But it print undefined.
I also tried Object.assign(t, toast()), or doing simply this = toast() in the constructor but it doesn't work.
I do not know what are you trying to do. May be you are looking for this.
const toast = () => {
return ({
getToast: () => {
return 'toast'
},
setToast: () => {
return 'wtf'
}
})
}
class t {
constructor(fn) {
return fn;
}
}
const tt = new t(toast())
console.log(tt.getToast())
console.log(tt.setToast())
For your exact scenario, that is if the function returns an object with only functions and no non-function properties, one way to do this is simply using prototype inheritance:
function t () {}; // though by convention a class should REALLY be uppercase
t.prototype = toast();
Now you can do:
let bread = new t();
let bagel = new t();
bread.getToast();
bagel.getToast();
You can also use a design pattern called parasitic inheritance but this is less memory efficient because just like the toast() function it creates a copy of the functions for each object:
class t {
constructor () {
let tmp = toast();
// Copy methods to our instance:
for (let prop in tmp) {
this[prop] = tmp[prop];
}
}
}
Or with Object.assign() it would simply be:
class t {
constructor () {
Object.assign(this, toast());
}
}
However, as mentioned, the prototype inheritance is the better mechanism for this use-case.
I've written a TypeScript decorator/JavaScript mixin around a React Component called autodispose. Suppose that class A extends autodispose(Component) and class B extends A. The mixin ensures that A.componentWillUnmount() is called regardless of whether B.componentWillUnmount() calls super.componentWillUnmount() via a proxy.
(The code is in TypeScript, but the question pertains to JavaScript.)
export function autodispose<
T extends Class<React.Component>
>(Target: T) {
const ObservingComponent = class extends Target {
constructor(...args: any[]) {
super(...args);
// ... mixin setup ...
this.componentWillUnmount = new Proxy(this.componentWillUnmount, {
apply: (target, thisArg, fnArgs) => {
Reflect.apply(target, thisArg, fnArgs);
if (super.componentWillUnmount) {
super.componentWillUnmount();
}
this.__mixinCleanup();
},
});
}
componentWillUnmount() {
if (super.componentWillUnmount) {
super.componentWillUnmount();
}
this.__mixinCleanup();
}
private __mixinCleanup() {
// is a no-op if __mixinCleanup() has already been called
// ...
}
};
}
If B calls super.componentWillUnmount() then the proxy will call A's componentWillUnmount() twice--first by Reflect.apply(target, thisArg, fnArgs) and then immediately after that. I need a way to detect if the call to Reflect.apply() has already called super.componentWillUnmount() and prevent the second call.
I considered temporarily overriding super.componentWillUnmount with another Proxy which sets a flag that it's been called, but, unsurprisingly, you can't override super's methods.
If all else fails, I can just make sure that autodispose doesn't get called in the prototype chain twice, but this solution would be more ideal.
You don't need to (temporarily or otherwise) overwrite B's super.componentWillUnmount - you did define A.prototype.componentWillUnmount yourself! You can just set the flag in there:
export function autodispose(Target) {
return class ObservingComponent extends Target {
constructor(...args) {
super(...args);
// ... mixin setup ...
this._hasSuperBeenCalled = false;
let original = this.componentWillUnmount;
this.componentWillUnmount = function(...args) {
this._hasSuperBeenCalled = false;
original.apply(this, args);
if (!this._hasSuperBeenCalled)
throw new Error(`Warning: ${this.constructor.name}.componentWillUnmount forgot its super call`);
};
}
componentWillUnmount() {
this._hasSuperBeenCalled = true;
if (super.componentWillUnmount) {
super.componentWillUnmount();
}
// ...mixin cleanup
}
};
}
I'm trying to create a class which allows passing a callback to alter the side-effects of a method. If you don't pass a callback, then the method will be called directly. This is a basic example:
class Button<T = void> {
private clickWrapper?: (click: Function) => T
private _click() {
// do the click here
return null;
}
constructor(clickWrapper?: (click: Function) => T) {
this.clickWrapper = clickWrapper;
}
public click() {
if (this.clickWrapper) {
return this.clickWrapper(this._click.bind(this));
} else {
return this._click();
}
}
}
class Foo {
public doStuff() {
console.log('hello');
}
}
const button = new Button<Foo>(click => {
// do some stuff
click();
return new Foo();
});
const foo = button.click();
foo.doStuff();
const button2 = new Button();
button2.click();
This works, but foo.doStuff() complains that foo may be null - even though in this case I provided a clickWrapper, so the return value of button.click() cannot be null, it must be an instance of Foo. Is there a better way to define this?
The second issue is I have to copy the Button constructor's parameter type when I've already declared it for Button.clickWrapper. How do I avoid having to declare the type on the private property and constructor parameter?
I have updated you code snippet:
class Button<T = null> {
constructor(private clickWrapper?: (click: Function) => T) {}
private _click() {
// do the click here
return null;
}
public click(): T {
if (this.clickWrapper) {
return this.clickWrapper(this._click.bind(this));
} else {
return this._click();
}
}
}
class Foo {
public doStuff() {
console.log("hello");
}
}
const button = new Button<Foo>(click => {
// do some stuff
click();
return new Foo();
});
const foo = button.click();
foo.doStuff();
const button2 = new Button();
button2.click();
Two things:
TypeScript can't be sure what is exact return type of your public click function so it assumes T | null, since default _click function returns null
To avoid redeclaring types for constructor and property of an object, you can always use shorthand syntax for constructor assignment (just add private or public keyword to constructor param)
interface Callback<V> {
(arg: () => void): V
}
class Button<T = void> {
constructor(private callback?: Callback<T>) {}
private onClick = () => {
}
public click = () => {
if (this.callback) {
return this.callback(this.onClick)
} else {
return this.onClick()
}
}
}
const button = new Button<number>(
click => {
click()
return 2 +2
}
)
console.log(button.click()) // 4
I update your code to solve your problems
Create an interface for the callback type and add the private callback? to the constructor to inject the argument to the class
There are many types for a function, in typescript a function that not return nothing is a void function, you are returning null, so that didn't match with your clickWrapper type, I assume you aren't gonna return anything from the click function so I update that type to match too with a void function