Import ES6 dependency inside class constructor - javascript

I have a JS/ES6 class with a constructor that needs to dynamically load a dependency based on the settings passed to it.
It works perfectly with CommonJS syntax:
class Example {
handler = null;
constructor(dependency) {
try {
this.handler = new (require(dependency))();
} catch {
throw new Error(`${dependency} not installed`);
}
}
/* ... */
}
However, I haven't been able to get it to work properly in ES6 module format. Here's as far as I've been able to get to:
class Example {
handler = null;
constructor(dependency) {
try {
this.handler = new (await import(dependency)).default();
} catch {
throw new Error(`${dependency} not installed`);
}
}
/* ... */
}
This doesn't work because you cannot use await without async, and a class constructor cannot be async.
Without await, further code tends to fail, as there is no guarantee the dependency has been loaded in time.
I cannot load the dependency outside of the class in the ordinary way, because it can be essentially any arbitrary dependency the consumer of the class defines.
I would prefer not to have a separate init/build method that the consumer needs to worry about. Ideally, you should be able to just do const ex = new Example('some-dependency') and then have instant access to the methods of the class in ex, without any need for further boilerplate on the consumer side.
The constructor could of course call a static build method, but that seems to just be moving the asynchronous problem to a different location.
Is there an established pattern or a kludgey hack to achieve this in ES6 modules?

Related

Modules vs Class memory difference

I have some methods and properties that I would like to be available to other files in my JS application.
I see I have 2 options, I can either have a class:
class MusicTile {
artistTitle = find('someProperty')
play() {
// Some logic
}
}
And then do let myTile = new MusicTiles
Or just export like so
let artistTitle = find('someProperty')
function play() {
// Some logic
}
export { play }
And just import { * } from './musicTile.js'
I'm curious which of the 2 is better to use. I don't really need to instantiate MusicTile, I just want the properties from there so I guess a plain old export is fine. But is there something else to consider?
Also how is memory usage between the two, I guess the export doesn't do anything unless I call the methods in there. But the class approach (at least how it is with the artistTitle = find('someProperty') outside a method) would call find() as soon as I do new MusicTile so consume more memory?

require exported typescript class in javascript

I'm moving my nodejs project from Javascript to Typescript. It's going to be a slow process, slowly changing things over a few months as i need to tweak stuff.
I've created a typescript class that looks something like this:
// RedisRepository.ts
export class RedisRepository {
public async getDevice(serial: string) : Promise<Device> {
// blah
return device;
}
}
Then in another Javascript file where i need to reference and then call the functions on the above class.
// ExpressApi.js
const repository = require('../Redis/RedisRepository');
async function getRedis(req, res) {
try {
const device = await repository.getDevice('serialnumberxxx');
res.status(200).end(JSON.stringify(device ));
} catch (e) {
logger.error(e);
res.status(500).end();
}
}
however, when it tried to call the function on the repository it says it doesn't exist. Using chrome debugger, i can see that it exists at: repository.RedisRepository.prototype.getDevice. This doesn't seem the correct way to use the function though.
While I appreciate I could just convert ExpressApi.js to Typescript. I'm going to have this problem with many different files. So i need to use it as JavaScript for now. I'll slowly continue to go round the project and change things to Typescript.
As #crashmstr mentioned, you should create a new instance of RedisRepository, then call getDevice method.
If you still want to use
const repository = require('../Redis/RedisRepository');
syntax, you could export default new RedisRepository() from your RedisRepository.ts file.

Pass TypeScript object to Window in Electron app

If I have a simple typescript class that just prints to the screen, like below, how can I access it on the front end in a simpler way?
speak.ts
export class Speak {
public write() {
console.log("Hello");
}
}
I know you are able to use
index.html
<script>
var x = require('./speak');
x.Speak.prototype.write(); // Prints "Hello"
</script>
The require statement has to assign to a variable for me to access this class. I'm not able to access it using require('./speak'); on its own, trying to bring it into global scope.
Having to preface every command with "x.Speak.prototype" is a bit verbose, and could easily become much longer when multiple classes and interfaces are introduced.
I feel like I'm not doing this the right way. How can I bring data/functions over from TypeScript classes to operate on the front end?
UPDATE
When I try something like below in my index.html file
<script>
var speak = new Speak();
speak.write("some other stuff");
</script>
I get an error: Uncaught ReferenceError: Speak is not defined
There are two things involved.
ES6 -> CommonJS interop
class syntax
For the first point, you are declaring an ES6 module while consuming it in commonJs syntax.
that's why you need the extra X to hold on to the module object in CJS:
var X = require('./speak');
var speak = new X.Speak();
// or accessing the `Speak` class directly:
var Speak = require('./speak').Speak;
var speak = new Speak();
If you consume the same code in ES6, it would be:
import { Speak } from './speak'
const s = new Speak();
// or
import * as X from './speak'
const s = new X.Speak();
Of course, ESM (ES6 Module system) is not available in every browser, so you need to transpile your TypeScript code down to ES5 and use some loader mechanism to load the module (like requireJS).
For the second point, you are writing a class. so you typically would create an instance of Speak and use it (following code assume you consume the code in a module, to avoid confusion with the first point):
var speak = new Speak();
speak.write();
If you don't need an instance, you can use a static method or just function:
export class Speak {
static write() { ... }
}
// usage:
Speak.write();
// function
export function write() { ... }
// usage:
write();

Get file name from javascript ES6 class reference in nodejs

Is there a way to get the file name from a reference to a class? Please note this example is over simplified to illustrate what I'm trying to do (don't start suggesting logging libraries please!)
//Logger1.js
class Logger1 {
}
//MainProcess.js
class MainProcess {
startChildProcess(Logger) {
//This extension doesn't work, but looking to find something similar that does:
Logger.fileName = () => {
return __filename
}
let loggerFileName = Logger.fileName() //Returns "Main.js" not "Logger1.js", so no good...
childProcess.fork(processFileName, [loggerFileName] )
}
}
//In a child process spawned by main:
loggerPath = process.argv[2]
let Logger = require(loggerPath)[path.basename(loggerPath).replace(".js","")]
let logger = new Logger()
I can obviously add a property with a string value, or a method to return __filename the Logger1 class, but I'd rather avoid it. Is there a way to do this from inside the MainProcess class, keeping Logger1 and any other external code clean?
The reason I don't pass the instance of the logger, is the main process then creates child processes, and these child processes instantiate their own loggers. And there is no way to pass object references down to child processes as far as I'm aware.

Injected dependencies not accessible in class methods for AngularJS

I'm using ES6 classes for Angular controllers and I'm running into a slight annoyance with dependency injection. Take this controller, for example (obviously not a real class).
class PersonController {
constructor(MyDependency) {
MyDependency.test(); // this works
}
sendEvent() {
MyDependency.test(); // MyDependency is undefined
}
}
PersonController.$inject = ['MyDependency'];
export default PersonController;
When the constructor is run, the dependency is found fine. However if I call the sendEvent method (or any other class method), the dependency is no longer defined. I've gotten around this before by just attaching it to this in the constructor e.g. this.MyDependency = MyDependency;.
Is there any other way that this can be achieved without hanging the dependency off this?
It is because myDependency is not accessible in your methods. Do this.myDependency = myDependency in the constructor and then do this.my dependency.test() in your method.
EDIT: Below is an alternative approach:-
let _myDependency;
class PersonController {
constructor(MyDependency) {
_myDependency = MyDependency;
_myDependency.test(); // this works
}
sendEvent() {
_myDependency.test(); // this will work
}
}
Another ways to emulate private properties are :- using Symbols/weakmaps or creating a closure around the whole class and store MyDependency within this closure.
Seems like there is no other way to do this, so I will stick to using this!

Categories

Resources