implement interface that has anonymous call signatures - javascript

I am trying to create an instance of this interface
interface Logger {
(...args: any[]): void;
error(...args: any[]): void;
warn(...args: any[]): void;
info(...args: any[]): void;
verbose(...args: any[]): void;
}
i.e it has to be both callable and it has to be possible to access the properties.
const log: Logger = ...implementation //this is what I am looking for
log('asd') // this should work
log.error('foo') // this should work
How do I achieve this behaviour?

Solution is the same as for this question. Credit to #SamaraSoucy-MSFT
You implement it by creating a closure that invokes immediately. Define the main method and add properties to it. Example below.
const log = (function () {
let main = function () { console.log(...arguments); }
main.info = function () { console.log(...arguments); };
main.error = function () { console.log(...arguments); };
//...other methods
return main;
})();
log('asd')
log.info('this is info')
log.error('this is error')

Related

How to conditionally set a function argument type in typescript?

I have a generic function I need to call in 2 places
const functionA = (arg1, deleteFunction) => {
deleteFunction(arg1)
}
when I call it in the two different places I pass in a different deleteFunction each time. these deleteFunctions then update redux but they require different types so I'm getting errors
I was wondering if for arg1 I could specify what type it should be based on properties it contains. something like this
const functionA = (arg1: arg1.hasSomeProperty ? Arg1Types : Arg1OtherType, deleteFunction) => {
deleteFunction(arg1)
}
obviously this doesn't work but the 2 deleteFunctions have different types (one has Arg1Typesthe other has Arg1OtherTypes
might be going about it in completely the wrong way. any ideas?
You can use a function overload, either using overload syntax with the function keyword, or using an interface with const an an arrow function as in your question.
Overload syntax:
function functionA(arg: Arg1Type, deleteFunction: (arg: Arg1Type) => void): void;
function functionA(arg: Arg1OtherType, deleteFunction: (arg: Arg1OtherType) => void): void;
function functionA(arg: any, deleteFunction: (arg: any) => void): void {
deleteFunction(arg);
}
Playground Link
A function interface with const an an arrow function:
interface FunctionA {
(arg: Arg1Type, deleteFunction: (arg: Arg1Type) => void): void;
(arg: Arg1OtherType, deleteFunction: (arg: Arg1OtherType) => void): void;
}
const functionA: FunctionA = (arg: any, deleteFunction: (arg: any) => void): void => {
deleteFunction(arg);
};
Playground link
In both cases, if Arg1Type is string and Arg1OtherType is number (for example), these calls work:
functionA("foo", (id) => {
// ...do the deletion...
});
functionA(42, (id) => {
// ...do the deletion...
});
...and these don't:
// Error: No overload matches this call.
// (because the types don't match)
functionA("foo", (id: number) => {
// ...do the deletion...
console.log(id);
});
// Error: No overload matches this call.
// (because no overload takes an object)
functionA({}, (id) => {
// ...do the deletion...
console.log(id);
});
And in both cases, only the overload signatures (the first two) will be shown by IDEs, etc.; the implementation signature isn't.
In a comment you've said:
...how the invoking of this functions knows which types to use? Arg1Type and Arg1OtherType are both objects but inside these objects, the types are differents for each property. ... I'd like to understand the conditional part a bit more
TypeScript will infer the correct overload to use based on the types of the arguments. In my examples, the types are string and number. When I started with functionA("foo", TypeScript could tell that I was using the string overload and will only allow a function that accepts a string. When I started with functionA(42, TypeScript could tell I was using the number overload and will only allow a function that accepts a number.
That's fine with objects with different shapes as well:
interface Arg1Type {
prop: string;
}
interface Arg1OtherType {
prop: number;
}
functionA({"prop": "foo"}, (obj) => {
// ...do the deletion...
console.log(obj);
});
functionA({"prop": 42}, (obj) => {
// ...do the deletion...
console.log(obj);
});
Playground Link
type A = string
type B = number
type Arg1 = A | B
const functionA = (arg1: Arg1, deleteFunc: (arg1: Arg1) => void): void {
deleteFunc(arg1);
}

Creating TypeScript class decorator for log correlation

I'm trying to create a TypeScript decorator that can be added to any class, that will create a request-scoped context that I can use to store a request ID. I've come across a couple of articles around decorators but none seem to fit my use-case.
Below is the code that I use to create the async hook, the decorator and a sample class that should be wrapped. When running the code, the actual response I receive is an empty object. The context is being lost but I'm not following why. I'm not receiving any errors or warnings.
Any suggestions would be highly appreciated.
This is the code I'm using to create the context. Calling the initContext() and getContext() functions.
import * as asyncHooks from 'async_hooks'
const contexts: any = {}
asyncHooks.createHook({
init: (asyncId: any, type: any, triggerAsyncId: any) => {
if (contexts[triggerAsyncId]) {
contexts[asyncId] = contexts[triggerAsyncId]
}
}
}).enable()
function initContext(fn: any) {
const asyncResource = new asyncHooks.AsyncResource('REQUEST_CONTEXT')
return asyncResource.runInAsyncScope(() => {
const asyncId = asyncHooks.executionAsyncId()
contexts[asyncId] = {}
return fn(contexts[asyncId])
})
}
function getContext() {
const asyncId = asyncHooks.executionAsyncId()
return contexts[asyncId] || {}
}
export { initContext, getContext }
This is the decorator that I'm using to wrap the class with. I'm trying to create the constructor within the context of the initContext function, set the context ID and then return the new constructor.
import { initContext } from './lib/context'
function AsyncHooksContext<T extends { new(...args: any[]): {} }>(target: T) {
return initContext((context: any) => {
context.id = 'some-uuid-goes-here'
return class extends target {
constructor(...args: any[]) {
super(...args)
}
}
})
}
export { AsyncHooksContext }
This is a sample class that should be able to produce the context ID
#AsyncHooksContext
class Foo {
public bar() {
const context = getContext()
console.log('This should be the context => ', { context })
}
}
new Foo().bar()

Class Decorator in Typescript

I'm trying to understand how class decorators work in Typescript when we wish to replace the constructor. I've seen this demo:
const log = <T>(originalConstructor: new(...args: any[]) => T) => {
function newConstructor(... args) {
console.log("Arguments: ", args.join(", "));
new originalConstructor(args);
}
newConstructor.prototype = originalConstructor.prototype;
return newConstructor;
}
#log
class Pet {
constructor(name: string, age: number) {}
}
new Pet("Azor", 12);
//Arguments: Azor, 12
Everything is understood but this line:
newConstructor.prototype = originalConstructor.prototype;
Why do we do that?
The classes like:
class Pet {
constructor(name: string, age: number) {}
dosomething() {
console.log("Something...");
}
}
Are compiled into functions when targeting ES5:
var Pet = (function () {
function Pet(name, age) {
}
Pet.prototype.dosomething = function () {
console.log("Something...");
};
return Pet;
}());
As you can seem when we use functions to define classes. The methods are added to the function's prototype.
This means that if you are going to create a new constructor (new function) you need to copy all the methods (the prototype) from the old object:
function logClass(target: any) {
// save a reference to the original constructor
const original = target;
// a utility function to generate instances of a class
function construct(constructor: any, args: any[]) {
const c: any = function () {
return constructor.apply(this, args);
};
c.prototype = constructor.prototype;
return new c();
}
// the new constructor behaviour
const newConstructor: any = function (...args: any[]) {
console.log("New: " + original.name);
return construct(original, args);
};
// copy prototype so intanceof operator still works
newConstructor.prototype = original.prototype;
// return new constructor (will override original)
return newConstructor;
}
You can learn more at "Decorators & metadata reflection in TypeScript: From Novice to Expert (Part I)"
Update
Please refer to https://github.com/remojansen/LearningTypeScript/tree/master/chapters/chapter_08 for a more recent version.

Typescript: Property does not exist

I'm trying to develop a decorator for REST Api Interfaces in Typescript. Here it is the decorator implementation
export function RemoteResource(params: any): Function {
console.log("RemoteResource.params: ", params);
return function (target: Function) {
//--POST
target.prototype.post = function () {
console.log("----POST");
};
//--GET
target.prototype.retrieve = function () {
console.log("----GET");
};
//--DELETE
target.prototype.remove = function () {
console.log("----DELETE");
};
//--PULL
target.prototype.update = function () {
console.log("----PULL");
};
console.log("RemoteResource.target: ", target);
return target;
}
}
Now, I can use the decorator #RemoteResource and the methods post|retrieve|remove|update are added to the original object prototype correctly.
#RemoteResource({
path: "/foos",
methods: [],
requireAuth: false
})
export class Foo { }
From here, if I execute
let tester = new Foo();
tester.post() //--This prints out "----POST" correctly
I've the log printed out correctly, but I've also have the following error: "Property 'post' does not exist on type 'Foo'."
While I understand why I'm having this error (Foo doesn't have any declared post property) I'm not sure about how to fix it.
Ideally, I would like that the TS compiler understand that the decorator extends the original object adding up those methods.
How can I achieve it? Any ideas?
Thanks!
Since you are adding these methods dynamically at runtime in the decorator, the compiler has no way of knowing that these methods will exist for Foo instances.
You can change that in different ways, for example:
(1) Using an interface and intersection:
interface RemoteResource {
post(): void;
remove(): void;
update(): void;
retrieve(): void;
}
let tester = new Foo() as Foo & RemoteResource;
tester.post(); // no error
(2) Interface and empty methods:
export class Foo implements RemoteResource {
post: () => void;
remove: () => void;
update: () => void;
retrieve: () => void;
}
let tester = new Foo() as Foo & RemoteResource;
tester.post();
Edit
#Robba suggests:
(3) Ignore all type checking
let tester = new Foo() as any;
tester.post();
or
let tester = new Foo();
tester["post"]();

How can I extend Backbone.Events in a Typescript ES6 Class?

I'm trying to attach Backbone's Events properties onto a TypeScript class, however when I do this...
class Foo {
constructor () {
_.assign(this, Backbone.Events); // or _.extend()
this.stopListening(this.otherInstance);
}
}
let bar = new Foo();
bar.on("myevent", handler);
...I get these compile time errors:
Error TS2339: Property 'stopListening' does not exist on type 'Foo'.
Error TS2339: Property 'on' does not exist on type 'Foo'.
I'm not very familiar with how TypeScript would approach this, but seems like something it could handle.
Note: looking for a solution that's easy to apply to multiple classes that also need Backbone.Events functionality (ie. I don't want to copy/paste all the on,off,listenTo... methods, or some funky proxy approach, to every class that needs them).
Since Backbone.Events is just an object, I can't extend it using normal ES6 syntax. Ex)
class Foo extends Backbone.Events {}
Ideas?
instead of _.assign if you use _.extend it will work,
Here is a Plunker
class Foo {
constructor () {
_.extend(this, Backbone.Events);
}
}
let bar : any = new Foo();
bar.on("alert", function(msg) {
alert("Triggered " + msg);
});
bar.trigger("alert", "an event");
updated code so that it does not gives compile time error.
UPDATE
you may create a class which has all the functions defined for Backbone.Events and constructor of it can extend Backbone.Events, which will override all the methods which is just defined for intellisense and type check.
updated the plunker
class CustomEvents {
constructor() {
_.extend(this, Backbone.Events);
}
on(eventName: string, callback?: Function, context?: any): any { return; };
off(eventName?: string, callback?: Function, context?: any): any { return; };
trigger(eventName: string, ...args: any[]): any { return; };
bind(eventName: string, callback: Function, context?: any): any { return; };
unbind(eventName?: string, callback?: Function, context?: any): any { return; };
once(events: string, callback: Function, context?: any): any { return; };
listenTo(object: any, events: string, callback: Function): any { return; };
listenToOnce(object: any, events: string, callback: Function): any { return; };
stopListening(object?: any, events?: string, callback?: Function): any { return; };
}
and you can extend any class with CustomEvents class like below,
class Foo extends CustomEvents {
constructor(){
super();
}
}
On Backbone.Events the event handling are attached on the object itself rather than on its .prototype, here is how you can correct that:
import {Events} from 'backbone';
interface IEventEmitter extends Events {
emit(event: string, ...args: any[]);
}
function _EventEmitter() {}
_EventEmitter.prototype = Events;
_EventEmitter.prototype.emit = (Events as any).trigger;
export const EventEmitter: new() => IEventEmitter
= _EventEmitter as any as new() => IEventEmitter;
Now use it as by inheritance:
class Dog extends EventEmitter {
}
var dog = new Dog;
dog.on('bark', () => console.log('Dog just barked'));
dog.emit('bark');
Use typings from here - https://www.npmjs.com/package/#types/backbone
Write implementation of Backbone.EventsMixin somewhere right after Backbone script is included:
Backbone.EventsMixin = function () {
_.assign(this, Backbone.Events);
}
Then it can be used like this:
class SomeClass extends Backbone.EventsMixin

Categories

Resources