I'm mocking an interface class:
const error = "Child must implement method";
class MyInterface
{
normalFunction()
{
throw error;
}
async asyncFunction()
{
return new Promise(() => Promise.reject(error));
}
}
class MyImplementation extends MyInterface
{
}
If any of the interface methods are called without an overridden implementation, the error gets thrown. However these errors will only appear at time of execution.
Is there a way to check that the functions were overridden at construction?
You could add some inspection in the constructor of MyInterface, like this:
class MyInterface {
constructor() {
const proto = Object.getPrototypeOf(this);
const superProto = MyInterface.prototype;
const missing = Object.getOwnPropertyNames(superProto).find(name =>
typeof superProto[name] === "function" && !proto.hasOwnProperty(name)
);
if (missing) throw new TypeError(`${this.constructor.name} needs to implement ${missing}`);
}
normalFunction() {}
async asyncFunction() {}
}
class MyImplementation extends MyInterface {}
// Trigger the error:
new MyImplementation();
Note that there are still ways to create an instance of MyImplementation without running a constructor:
Object.create(MyImplementation.prototype)
Can't you use reflection in order to list all the function of a class?
For example here https://stackoverflow.com/a/31055217/10691359 give us a function which list all function of an object. Once you get all of them, you can see if you have or not an overridden function.
Related
Typescript newbie here.
Let's say I have a type coming from a library that looks like this:
type FooType {
name: string;
// Has much more attributes in real life
}
I now want to define a class called Foo like this:
import { FooType } from 'my-library';
class Foo {
constructor(data: FooType) {
Object.assign(this, data);
}
}
With this code, I'm able to define a foo instance, yet I have an issue with autocomplete:
const foo = new Foo({ name: 'foo name' });
// Typing in foo.name does not bring any type information about the "name" attribute
Is there a way I can make a class automatically "inherit" all attributes from a type without having to type them manually?
Edit after being marked as duplicate:
What I want to achieve is to avoid manually typing attributes that are already existing on a type.
Thanks to #Phil I've been provided an answer that mentions this as an ongoing issue within Typescript:
https://github.com/microsoft/TypeScript/issues/26792
What I will do for now is the following:
class Foo {
constructor(public _data: FooType)
Object.assign(this, _data);
}
get() {
return this._data;
}
}
const foo = new Foo({ name: 'Bar' });
foo.get().name; // After typing "foo.get()" the autocomplete works properly.
Note: this is just a workaround and doesn't fix the underlying issue with using Object.assign() in the constructor with TypeScript.
https://github.com/Microsoft/TypeScript/issues/16672
Update: this question definitely lead me down quite the rabbit and I wasn't quite happy with my previous response so here is something I think would work better for solving these two items:
Creating a class which extends an object
Allowing TypeScript to still do its thing
Step One: use the typescript mixin pattern to return an anonymous class with the properties of a generic object T:
function GenericBase<T>(data: T) {
return class {
constructor() {
Object.assign(this, data)
}
} as ({
new (...args: any[]): T;
})
}
Now we have an anonymous function with a constructor which is then casted as a class that returns our generic object T.
Step Two: Note that we don't need to touch the above code we just need to extend it, so for OP's example we would do this:
class MyClass extends GenericBase(foo) {
constructor() {
super()
}
greet() {
return `Hello, ${this.name}!`
}
}
More examples:
const myClass = new MyClass()
console.log(myClass.name) // bar
console.log(myClass.greet()) // Hello, bar!
function doSomethingWithFoo(someFoo: Foo) {
// example method that only takes types Foo
}
doSomethingWithFoo(myClass)
Try it on the TypeScript playground!
I am currently looking at learning Typescript Decorators. My first goal is to somewhat reproduce what #Slf4J from the Project Lombok does in Java to Typescript. The ideas is to annotate/decorate a class with e.g. #logger to receive a field log of type LogUtil within that same class in order to call e.g. log.info().
LogUtil class:
export class LoggerUtil {
logLevel: LogLevel;
constructor(logLevel: LogLevel) {
this.logLevel = logLevel;
}
error(className: string, message: string) {
if (this.logLevel >= LogLevel.ERROR) {
console.error(`${new Date()} [ERROR] ${className}: ${message}`);
}
}
warn(className: string, message: string) {
if (this.logLevel >= LogLevel.WARN) {
console.log(`${new Date()} [WARN] ${className}: ${message}`);
}
}
log(className: string, message: string): void {
console.log(`${new Date()} [LOG] ${className} ${message}`)
}
info(className: string, message: string): void {
if (this.logLevel >= LogLevel.INFO) {
console.log(`${new Date()} [INFO] ${className}: ${message}`)
}
}
call(className: string, message: string) {
if (this.logLevel >= LogLevel.INFO) {
console.log(`${new Date()} [CALL] ${className}.${message}`)
}
}
debug(className: string, message: string) {
if (this.logLevel >= LogLevel.DEBUG) {
console.log(`${new Date()} [DEBUG] ${className}: ${message}`)
}
}
}
LogLevel enum:
export enum LogLevel {
ERROR = 0,
WARN = 1,
INFO = 2,
DEBUG = 3
}
Example class using the #logger decorator to get an instance of LoggerUtil as log
#logger
export class SomeService {
exampleFunction() {
log.info("exampleFunction called")
}
}
I am currently trying to do this with the class-level decorators. Here I am trying to do different things:
Using the Reflect API to define a property on the class. Here I am not even sure if that even works.
export function logger() {
return function(target: Function) {
Reflect.defineProperty(target, "log", { value: new LoggerUtil(LogLevel.DEBUG) } )
}
}
Using the class prototype to define a property:
export function logger() {
return function(target: Function) {
target.prototype.log = new LoggerUtil(LogLevel.DEBUG);
}
}
With every approach I am getting "Cannot find name 'log'" when referencing the log instance within the Service:
#logger
export class SomeService {
exampleFunction() {
log.info("exampleFunction called") // Cannot find name 'log'
}
}
Is my idea possible at all? Is there something fundamental that I am missing?
Thank you vey much in advance for any feedback!
I found an approach that satisfies my needs at least a bit:
I went with inheritance (which I'd like to avoid). See below example:
#logger
export class SomeService extends Logger {
constructor() {
super()
}
exampleFunction() {
log.info("exampleFunction called")
}
}
Then the super class I am extending looks like this:
#logger
export class Logger {
log: LoggerUtil;
}
And finally, the decorator looks like this:
export function logger<T extends {new(...args:any[]):{}}>(constructor:T) {
return class extends constructor {
log = new LoggerUtil(LogLevel.DEBUG);
}
}
My idea was to add the field log to SomeService via inheritance. To initialize this field now, I used the decorator on the super class. The decorator itself returns a class that extends the decorator class with the initialized field. This way the following inheritance graph is created:
#logger (Decorator) -> Logger (super class) -> SomeService.
I could have initialized the log field within the super class Logger itself. However, this was to look into decorators and hopefully erase the super class in the long run.
As a reference I want to point to the Typescript documentation about how to overwrite the constructor.
I created some custom Erros in my application and I wanted to check for them later using the constructor name. The problem is that when I extend Error in my classes, the constructor.name is always "Error", not the name I actually gave to it.
I was doing some tests and noticed that this happens with the Error class, but not with any other custom class I create.
e.g.:
class CustomClass {
constructor(msg) {}
}
class OtherClass extends CustomClass {
constructor(msg) {
super(msg);
}
class CustomError extends Error {
constructor(msg) {
super(msg);
}
}
const e = new CustomError("There was an error");
const otherClass = new OtherClass("This is a class");
console.log(otherClass.constructor.name); // "OtherClass" <- ok!
console.log(e.constructor.name); // "Error" <- not ok! expected "CustomError"
Does anyone knows why this happens?
I thought I could do something like:
class CustomError extends Error {
constructor(msg) {
super(msg);
}
getName() {
return "CustomError";
}
}
const e = new CustomError("There was an error");
if(e.getName() == "CustomError") {
// do stuff
}
But then I get: TypeError: e.getName is not a function
Does anyone know if there is a way to override the constructor name when extending Error?
Also, why can't I declare and call methods in my CustomError error class?
EDIT
Following #samanime suggestion, I updated my node version to 8.8.1 and was able to find a partial solution.
Changing the syntax a little bit I got:
const FileSystemException = module.exports = class FileSystemException extends Error {
constructor(msg) {
super(msg);
}
getName() {
return "FileSystemException";
}
}
const e = new FileSystemException("There was an error");
// Running node app.js
console.log(e.constructor.name); // "FileSystemException"
console.log(e.getName()); // "FileSystemException"
// Running babel-node app.js
console.log(e.constructor.name); // "Error"
console.log(e.getName()); // "TypeError: e.getName is not a function"
Still, it would be awesome if someone could make it work with babel so I can use import/export statements without having to wait for node v9.4 LTS.
Using:
node v8.8.1
babel-node v6.26.0 w/ "es2015" and "stage-0" presets
thanks!
It seems to work just fine in this simple example:
class CustomError extends Error {
constructor(msg) {
super(msg);
}
}
const error = new CustomError()
console.log(error.constructor.name);
console.log(error instanceof CustomError);
console.log(error instanceof Error);
That is running with native class support (in Chrome).
It is possible that it isn't an issue with your syntax, but an issue with your transpiler. You probably want to dig through the transpiled code itself and see if it is doing anything special with Error that it doesn't with normal classes.
The fact that your getName() example didn't work also seems to indicate something funky is going on. Your examples you've posted look good. Double-check the code you are trying to run actually matches them.
A simple solution could be as follow
class CustomError extends Error {
constructor(msg) {
super(msg);
}
get name(){
return 'CustomError'
}
}
const e = new CustomError("There was an error");
console.log(e.constructor.name); // "CustomError"
console.log(e.name); // "CustomError"
This will also change the error name in the stack trace so when you throw your custom error object you will see:
CustomError: some random stack trace
Instead of:
Error: some random stack trace
For more information about using 'get' in js classes/objects, you can take a look at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
Hope I am not too late to the party and that you find this helpful.
To make error.name correctly set, you can create a base class for all your custom errors.
class MyAbstractError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
class NotFoundError extends MyAbstractError {}
class BadParameterError extends MyAbstractError {}
console.log( (new BadParameter("x")).name) // => "BadParameter"
console.log( (new NotFoundError ("x")).name) // => "NotFoundError"
Or you can simply add a name() getter in your base class that returns this.constructor.name
Could you please help to understand what can be wrong while using promises in callback on parent class and then trying to set properties on child class?
I'm using nodejs v8.2.1
Here is example of base class:
class CGLBase extends CAuthClient
{
constructor(req, res)
{
return new Promise(resolve => {
super(req, res, (authClient) => {
this.setAuthClient(authClient);
resolve(this);
});
});
}
setAuthClient(authClient)
{
//setting authClient (this.auth will contain property)
}
}
And here example of child class:
class СSheet extends CGLBase
{
constructor(document, urlRequest, urlResponse)
{
super(urlRequest, urlResponse);
this.document = document;
this.someAnotherProp = "some property";
//etc..
}
someFunc()
{
//using this.document and this.auth
}
}
After that I'm creating instance of СSheet and trying to set properties:
var document = { ... }; //here I create object
(async () => {
var docSheet = await new СSheet (document, req, res);
docSheet.someFunc();
console.log(docSheet.auth); //return correct property
console.log(docSheet.document); //return undefined. why...
})();
So, I don't understand why property this.document wasn't set. I see only this.auth that was set in the async callback. All properties except this.auth are undefined.
I will be very grateful for advice or help.
Thank you in advance.
I don't understand why property this.document wasn't set. I see only this.auth that was set in the async callback.
Your CSheet constructor did set the document and someAnotherProp properties on the promise returned by super(). awaiting the new CSheet gives you the CAuthClient instance that the promise was resolved with, and which does not have the properties.
You could work around that by using
class СSheet extends CGLBase {
constructor(document, urlRequest, urlResponse) {
return super(urlRequest, urlResponse).then(instance => {
instance.document = document;
instance.someAnotherProp = "some property";
// …
return instance;
});
}
…
}
but you really absolutely never should do that. This of course is the fault of CAuthClient taking an asynchronous callback. Fix that class and all its children to do the asynchronous creation of authClient in a static helper method, and only pass plain values into the constructor.
I have created a TypeScript interface for my service results. Now I want to define a basic functionality for both my functions inside. The problem is I get an error:
The property 'ServiceResult' does not exist on value of type 'Support'.
I use WebStorm for development (VS2012 makes me nervous because on freezes by large projects - waiting for better integration:P).
Here's how I do it:
module Support {
export interface ServiceResult extends Object {
Error?: ServiceError;
Check?(): void;
GetErrorMessage?(): string;
}
}
Support.ServiceResult.prototype.Check = () => {
// (...)
};
Support.ServiceResult.prototype.GetErrorMessage = () => {
// (...)
};
I have also tried to move my prototypes into the module, but same error still... (of course I removed Support. prefix).
You can't prototype an interface because the compiled JavaScript does not emit anything related to the interface at all. The interface exists purely for compile-time use. Take a look at this:
This TypeScript:
interface IFoo {
getName();
}
class Foo implements IFoo {
getName() {
alert('foo!');
}
}
Compiles to this JavaScript:
var Foo = (function () {
function Foo() { }
Foo.prototype.getName = function () {
alert('foo!');
};
return Foo;
})();
There is no IFoo in the result, at all - which is why you are getting that error. Typically you wouldn't prototype an interface, you would prototype a class that implements your interface.
You don't even have to write the prototype yourself, just implementing the interface as a class is enough and the TypeScript compiler will add the prototype for you.
It looks like you are trying to add implementation to an interface - which isn't possible.
You can only add to a real implementation, for example a class. You may also decide to just add the implementation to the class definition rather than directly using prototype.
module Support {
export interface ServiceResult extends Object {
Error?: ServiceError;
Check?(): void;
GetErrorMessage?(): string;
}
export class ImplementationHere implements ServiceResult {
Check() {
}
GetErrorMessage() {
return '';
}
}
}
Support.ImplementationHere.prototype.Check = () => {
// (...)
};
Support.ImplementationHere.prototype.GetErrorMessage = () => {
// (...)
};