0

I created a FileHandler class with methods to read a file or many files like this.

export class FileHandler {
    private static async readFileFromFileQuery (fq: FileQuery): Promise<File> {
        const { path, encoding, flag } = FileQueryHandler.make(fq);
        const content = await promisify(fs.readFile)(path, { encoding, flag })
        return { path, encoding, flag, content };
    }
    static async readFile (a: Path | FileQuery, b?: Omit<FileQuery, 'path'>): Promise<File> {
        if (typeof a === 'string') a = FileQueryHandler.getFromPath(a, b);
        return FileHandler.readFileFromFileQuery(a);
    }
    static async readFiles (a: (Path | FileQuery)[] | Directory, b?: Omit<FileQuery, 'path'>): Promise<File[]> {        
        if (a instanceof Array) return Promise.all(a.map(p => this.readFile(p, b)));
        return FileHandler.readFiles(PathHandler.getFromDirectory(a), b);
    }
    static async readFilesFromDirectory(a: Path | FileQuery, b?: Omit<FileQuery, 'path'>): Promise<File[]> {
        const ps = await DirectoryHandler.readDirectory(a);    
        if (typeof a === 'string') return await FileHandler.readFiles(ps, b);
        return await FileHandler.readFiles(ps, a);
    }
}

This is a class with static methods because I don't need them to be public.

What I would like to do now is expand upon this class.

I would like to wrap the FileHandler.readFile in a try catch and possibly return null, something like this, where the method is named readFile but the return value is different from the original.

export class FileOrNullHandler {
    async readFile (a: Path | FileQuery, b?: Omit<FileQuery, 'path'>): Promise<File | null> {
        return orNull(() => FileHandler.readFile(a, b));
    }
}

I would also like to get all the other methods for free because what I see is all the original FileHandler are based on FileHandler.readFile.

I've tried some ways of converting both to not use static methods, and I've also experimented with return types, but nothing is jumping out at me as the simple way to do this.

What I need is the following:

  • Have parity between methods within both classes
  • Have all the return values represent the class functionality

I am looking for the best way to do this.

Ideally, the first thing that comes to mind would be I would be able to use

  • static methods that call this
  • ReturnType<this.method>

But both of those things do not exist.

What I want is the following result:

  • FileHandler.readFile that returns File | null
  • FileHandler.readFiles that returns File[]
  • FileHandler.readFilesFromDirectory that returns File[]
  • FileOrNullHandler.readFile that returns (File | null)
  • FileOrNullHandler.readFiles that returns (File | null)[]
  • FileOrNullHandler.readFilesFromDirectory that returns (File | null)[]
4
  • 1
    You should not use a class at all. Classes are meant to create instances, not to be used as a collection of static "methods". You are looking for TypeScript namespaces or JavaScript object literals. Commented Feb 3, 2019 at 14:55
  • @Bergi sure, can you please show how you'd expand on a method set using another syntax? Commented Feb 3, 2019 at 14:56
  • Using this to refer to the methods looks good, you could then extend your FileHandler object, using either inheritance with Object.create or a copy with Object.assign, and then overwrite the readFile function. I have no idea how to type this properly in TypeScript though, so I won't post an answer. Commented Feb 3, 2019 at 15:00
  • Interesting, i have few notes: Why did you need to extend some methods only to change its return type ? If as you mentioned you only need to return file | null, you can do it using typescript. Also if you need to extend some functionality you should add it to a super class and extends that class. So you can also override the base class method. Finally, using static methods inside normal class does not make sense for me. I suggest you to add utilities which should be static classes or namespaces. Commented Feb 3, 2019 at 15:02

3 Answers 3

1

First of all it makes little sense to have a class just with static properties, thats just a very complicated way to build up an object:

export const FileHandler = {
   readFile (a: Path | FileQuery, b?: Omit<FileQuery, 'path'>): Promise<File> {
     // ...
   }
};

The you can create the wrapper as:

 const FileOrNullHandler = Object.assign(...Object.keys(FileHandler).map(key => ({
   [key]: function(...args) {
     return orNull(() => FileHandler[key](...args);
   }
 })));
Sign up to request clarification or add additional context in comments.

1 Comment

How would the methods imported over to FileOrNullHandler know to now use the 'local' ReadFile method?
0

It seems that static class methods can use this, and extend. However the types are still not accurate.

export class FileHandler {
    static async readFileFromFileQuery (fq: FileQuery): Promise<File> {
        const { path, encoding, flag } = FileQueryHandler.make(fq);
        const content = await promisify(fs.readFile)(path, { encoding, flag })
        return { path, encoding, flag, content };
    }
    static async readFile (a: Path | FileQuery, b?: Omit<FileQuery, 'path'>): Promise<File> {
        if (typeof a === 'string') a = FileQueryHandler.getFromPath(a, b);
        return this.readFileFromFileQuery(a);
    }
    static async readFiles (a: (Path | FileQuery)[] | Directory, b?: Omit<FileQuery, 'path'>): Promise<File[]> {        
        if (a instanceof Array) return Promise.all(a.map(p => this.readFile(p, b)));
        return this.readFiles(PathHandler.getFromDirectory(a), b);
    }
    static async readFilesFromDirectory(a: Path | FileQuery, b?: Omit<FileQuery, 'path'>): Promise<File[]> {
        const ps = await DirectoryHandler.readDirectory(a);    
        if (typeof a === 'string') return await this.readFiles(ps, b);
        return await this.readFiles(ps, a);
    }
}

export class FileOrNullHandler extends FileHandler {
    static async readFileFromFileQuery (fq: FileQuery): Promise<File | null> {
        return orNull(() => FileHandler.readFileFromFileQuery(fq));
    }
}

Comments

0

I found this way of achieving this functionality that maintains static classes, but the types aren't properly represented in FileOrNullHandler.

export function sibling (v) {
    const staticClassName = v.toString().split ('(' || /s+/)[0].split (' ' || /s+/)[1];
    const m = {
        FileHandler,
        FileOrNullHandler
    }
    return m[staticClassName];
}

export class FileHandler {
    static async readFileFromFileQuery (fq: FileQuery): Promise<File> {
        const { path, encoding, flag } = FileQueryHandler.make(fq);
        const content = await promisify(fs.readFile)(path, { encoding, flag })
        return { path, encoding, flag, content };
    }
    static async readFile (a: Path | FileQuery, b?: Omit<FileQuery, 'path'>): Promise<File> {
        if (typeof a === 'string') a = FileQueryHandler.getFromPath(a, b);
        return sibling(this).readFileFromFileQuery(a);
    }
    static async readFiles (a: (Path | FileQuery)[] | Directory, b?: Omit<FileQuery, 'path'>): Promise<File[]> {        
        if (a instanceof Array) return Promise.all(a.map(p => sibling(this).readFile(p, b)));
        return sibling(this).readFiles(PathHandler.getFromDirectory(a), b);
    }
    static async readFilesFromDirectory(a: Path | FileQuery, b?: Omit<FileQuery, 'path'>): Promise<File[]> {
        const ps = await DirectoryHandler.readDirectory(a);    
        if (typeof a === 'string') return await sibling(this).readFiles(ps, b);
        return await sibling(this).readFiles(ps, a);
    }
}

export class FileOrNullHandler {
    static async readFileFromFileQuery (fq: FileQuery): Promise<File | null> {
        return orNull(() => FileHandler.readFileFromFileQuery(fq));
    }
    static readFile = FileHandler.readFile;
    static readFiles = FileHandler.readFiles;
    static readFilesFromDirectory = FileHandler.readFilesFromDirectory;
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.