This is a document containg many different examples of how to create the same base code. Each example is different, contains a different API, or different added functionality such as cashing.
- Example A
- Example B
- Example C
- Example D
- Example E
- Example F
- Example W
- Example X
- Example W.1
- Example Y
- Example Z
- Example Middleware
- Example Pure Functional Class
- Example object with methods
- No constructor arguments
- No use of any internal / external caching
- No use any internal
this
- Uses
static
methods for organization
class FileLoader {
path: string | null = null
cwd: string | null = null
constructor () {}
static getFullPath(options: { path: string, cwd: string }) {
if (nodePath.isAbsolute(options.path)) return options.path
return nodePath.join(options.cwd, options.path)
}
static getExtension(options: { path: string, cwd: string }) {
return nodePath.extname(options.path)
}
static async getContents (options: { fullPath: string }) {
return util.promisify(fs.readFile)(options.fullPath, { encoding: 'utf8' })
}
async main (options: { path: string, cwd: string }) {
const fullPath = FileLoader1.getFullPath(options)
const extension = FileLoader1.getExtension(options)
const contents = FileLoader1.getContents({ fullPath })
return { ...options, fullPath, extension, contents }
}
}
const options = { path: 'README.md', cwd: './'}
FileLoader.main(options).then(console.log)
- All methods return value
- All methods don't call each other
.main()
must call each method in order and assignthis
values- No external use of calling methods directly
class FileLoader1 {
path: string | null = null
cwd: string | null = null
constructor (private readonly options: { path: string, cwd: string }) {
this.path = this.options.path
this.cwd = this.options.cwd
}
fullPath: string | null = null
private getFullPath() {
return nodePath.isAbsolute(this.path) ? this.path : nodePath.join(this.cwd, this.path);
}
extension: string | null = null
private getExtension() {
return nodePath.extname(this.path)
}
contents: string | null = null
private async getContents () {
return util.promisify(fs.readFile)(this.fullPath, { encoding: 'utf8' })
}
async main () {
this.fullPath = this.getFullPath()
this.extension = this.getExtension()
this.contents = await this.getContents()
return this
}
}
- All methods must assign value to
this
and notreturn
.main()
must call each method in order
class FileLoader2 {
path: string | null = null
cwd: string | null = null
constructor (private readonly options: { path: string, cwd: string }) {
this.path = this.options.path
this.cwd = this.options.cwd
}
fullPath: string | null = null
private getFullPath() {
this.fullPath = nodePath.isAbsolute(this.path) ? this.path : nodePath.join(this.cwd, this.path);
}
extension: string | null = null
private getExtension() {
this.extension = nodePath.extname(this.path)
}
contents: string | null = null
private async getContents () {
if (!this.fullPath) throw new Error('missing fullPath');
this.contents = util.promisify(fs.readFile)(this.fullPath, { encoding: 'utf8' })
}
async main () {
this.getFullPath()
this.getExtension()
await this.getContents()
return this
}
}
- All methods return value
- Use of
.this
for constructor args - Directly pass in computed values (eg.
this.contents(fullPath)
)
class FileLoader {
constructor(private readonly options: {
cwd: string
path: string
}) { }
fullPath() {
const { path, cwd } = this.options
return nodePath.isAbsolute(path) ? path : nodePath.join(cwd, path)
}
extention() {
const { path } = this.options
return nodePath.extname(path)
}
async contents(fullPath: string) {
return readFile(fullPath, { encoding: 'utf8' })
}
async main() {
const fullPath = this.fullPath()
const extension = this.extention()
const contents = await this.contents(fullPath)
return {
...this.options,
fullPath,
extension,
contents
}
}
}
- Methods cache value
- Sibling functions can call upon sibling functions directly because cached
class FileLoader {
constructor(private readonly options: {
cwd: string
path: string
}) { }
_fullPath: string | null = null
fullPath() {
if (this._fullPath) return this._fullPath
const { path, cwd } = this.options
this._fullPath = nodePath.isAbsolute(path) ? path : nodePath.join(cwd, path)
return this._fullPath
}
_extention: string | null = null
extention() {
if (this._extention) return this._extention
const { path } = this.options
this._extention = nodePath.extname(path)
return this._extention
}
_contents: string | null = null
async contents() {
if (this._contents) return this._contents
this._contents = await readFile(this.fullPath(), { encoding: 'utf8' })
return this._contents
}
async main() {
return {
...this.options,
fullPath: this.fullPath(),
extention: this.extention(),
contents: await this.contents(),
}
}
}
- Use of getters for non async processes (will be problematic for async-to-async dependency)
class FileLoader {
constructor(private readonly options: {
cwd: string
path: string
}) { }
get fullPath() {
const { path, cwd } = this.options
return nodePath.isAbsolute(path) ? path : nodePath.join(cwd, path)
}
get extention() {
const { path } = this.options
return nodePath.extname(path)
}
async contents() {
return readFile(this.fullPath, { encoding: 'utf8' })
}
async main() {
return {
...this.options,
fullPath: this.fullPath,
extention: this.extention,
contents: await this.contents(),
}
}
}
- Purely functional
const getFullPath = (o: { path: string, cwd: string }) => {
return nodePath.isAbsolute(o.path) ? o.path : nodePath.join(o.cwd, o.path)
}
const getExtension = (path: string) => {
return nodePath.extname(path)
}
const getContents = (path: string) => {
return util.promisify(fs.readFile)(path, { encoding: 'utf8' })
}
const fileLoader = async (o: { path: string, cwd: string }) => {
const fullPath = getFullPath(o)
const extension = getExtension(fullPath)
const contents = await getContents(fullPath)
return { ...o, fullPath, extension, contents }
}
- Each method "private"
- Each function has no arguments
- Each method bound with parent
this
function fullPath() {
return nodePath.isAbsolute(this.path) ? this.path : nodePath.join(this.cwd, this.path);
}
function extension() {
return nodePath.extname(this.path);
}
function contents() {
return util.promisify(fs.readFile)(this.fullPath, { encoding: 'utf8' });
}
function fileLoader(o: { cwd: string, path: string }) {
this.cwd = o.cwd
this.path = o.path
this.fullPath = fullPath.bind(this)();
this.extension = extension.bind(this)();
this.contents = await contents.bind(this)();
return this;
}
function fullPath() {
this.fullPath = nodePath.isAbsolute(this.path) ? this.path : nodePath.join(this.cwd, this.path);
}
function extension() {
this.extension = nodePath.extname(this.path);
}
async function contents() {
this.contents = await util.promisify(fs.readFile)(this.fullPath, { encoding: 'utf8' });
}
function fileLoader(o: { cwd: string, path: string }) {
this.cwd = o.cwd
this.path = o.path
fullPath.bind(this)();
extension.bind(this)();
await contents.bind(this)();
return this;
}
- Not a class
- Assign direct values in function
async function fileLoader (o: {
cwd?: string,
path?: string
} ) {
const fullPath = nodePath.isAbsolute(o.path) ? o.path : nodePath.join(o.cwd, o.path)
const extension = nodePath.extname(o.path)
const contents = await util.promisify(fs.readFile)(fullPath, { encoding: 'utf8' })
return {...o, fullPath, extension, contents }
}
- Not a class
- Single function with
iife
to encapsulate value
async function fileLoader (o: {
cwd?: string,
path?: string
} ) {
const fullPath = (() => {
return nodePath.isAbsolute(o.path) ? o.path : nodePath.join(o.cwd, o.path)
})();
const extension = (() => {
return nodePath.extname(o.path)
})();
const contents = await (async () => {
return util.promisify(fs.readFile)(fullPath, { encoding: 'utf8' })
})();
return {...o, fullPath, extension, contents }
}
const nodePath = require('path');
const fs = require('fs');
function fullPath(context) {
console.log('fullPath')
context.fullPath = nodePath.isAbsolute(context.path) ? context.path : nodePath.join(context.cwd, context.path);
}
function extension(context) {
console.log('extension')
context.extension = nodePath.extname(context.path);
}
function contents(context, callback) {
console.log('contents')
return fs.readFile(context.fullPath, { encoding: 'utf8' }, (err, contents) => {
console.log('contents-i')
if (err) return callback(err);
context.contents = contents;
return callback();
});
}
function middleware(stack) {
return (context = {}, master) => {
var hasError = false
return stack.reduceRight((callback, fn) => {
if (hasError) return () => {};
return () => {
const isAsync = fn.length === 2
if (isAsync) {
fn(context, (err) => {
if (err) {
hasError = true
return master(err);
}
callback(null, context);
})
} else {
fn(context)
callback(null, context)
}
}
}, master)()
}
}
const fileLoader = middleware([fullPath, extension, contents])
fileLoader({ path: './example.md', cwd: '' }, (err, context) => {
console.log({ err, context })
})
const nodePath = require('path');
const fs = require('fs');
const util = require('util');
const getFullPath = (o) => {
return nodePath.isAbsolute(o.path) ? o.path : nodePath.join(o.cwd, o.path);
};
const getExtension = (path) => {
return nodePath.extname(path);
};
const getContents = (path) => {
return util.promisify(fs.readFile)(path, { encoding: 'utf8' });
};
const fileLoader = async (o) => {
const fullPath = getFullPath(o);
const extension = getExtension(fullPath);
const contents = await getContents(fullPath);
return Object.assign(Object.assign({}, o), { fullPath, extension, contents });
};
class FileLoader {
constructor(o) {
this.path = o.path;
this.cwd = o.cwd;
this.fullPath = getFullPath(this);
this.extension = getExtension(this.fullPath);
}
async getContents() {
return getContents(this.fullPath);
}
async main() {
const [contents] = await Promise.all([this.getContents()]);
return Object.assign(Object.assign({}, this), { contents });
}
}
new FileLoader({ cwd: '', path: '/Users/thomasreggi/Desktop/example.md' }).main().then(console.log);
const nodePath = require('path');
const fs = require('fs');
const util = require('util');
const FileLoader = {
fullPath() {
this.fullPath = nodePath.isAbsolute(this.path) ? this.path : nodePath.join(this.cwd, this.path);
},
extension() {
this.extension = nodePath.extname(this.path);
},
async contents() {
this.contents = await util.promisify(fs.readFile)(this.fullPath, { encoding: 'utf8' });
},
async main(o: { cwd: string, path: string }) {
this.cwd = o.cwd
this.path = o.path
this.fullPath();
this.extension();
await this.contents();
return this;
}
}
FileLoader.main({ cwd: '', path: '/Users/thomasreggi/Desktop/example.md'}).then(console.log)