Consider I have a.js with following class
class Connector {
constructor (url) {
this.url = url;
this.conneciton = null;
}
async connect() {
this.connection = await someThidPartyModule.connect(url);
return this;
}
}
// here I would like to do something like
// export default new Connector().connect();
Then use in b.js, c.js etc. connection from resovled connect method:
import Connector from 'a.js';
Connector.connection.callSomeMethod(); // here connection already exists after that promise resolved
As far as I aware it is not possible to do this, but maybe the some hack or workaround exists?
So after some tries found following:
export Promise is not a good idea, as on each import we also will get Promise, event if it is already resolved
Better to export class instance, call connect method and then allow to use it in all other files
Sadly anyway promise moving up to main endpoint file, where we have to make initialization async function and inside wait for promise to resolve
Also tried to export class with static create method, but that leave us with instance on the far end, which cannot be exported to other files.
Related
I have many async functions in my system, so I need to go "async all the way down", which is to the point where the http.Server and express.Application app are created.
(This is unavoidable in an async system - there will be many async routines which are needed in constructors, which cannot be done, and so we need to use async factory functions instead, which lead to async creep all the way down to the entry point.)
But I'm not sure of the Node/TypeScript syntax to use to bootstrap the app.
My main entry point is System.ts:
class default export System {
public constructor() {
// init Express.Application
// init http.Server
// init other parts of the system
}
public async start(): Promise<void> {
// start the system asynchronously
// start listening with http.Server
}
}
Then I have a bootstrapping module Main.ts:
import System from "./System"
const system = new System();
export default ???; // PROBLEM IS HERE
Which should be run:
node ./dist/Main.js
But I'm not sure what to use in the export line. I tried all these:
export default await system.start(); // doesn't compile (obviously)
export default system.start(); // doesn't seem right
export default system.start().then(); // this works *maybe*
The last line works based on a smoke test - but I'm not sure if that's the way to do it, and whether there's something down the line that may fail.
What is the canonical way to start an asynchronous node app?
UPDATE
Based on #JacobGillespie's answer, the Main.ts bootstrapping module is now:
import System from "./System"
new System().start().then();
//new System().start().catch(e => console.error(e)); // alternative
In my case, System.ts has handlers for errors and unhandled promises, and does logging (otherwise use the "alternative" line). So the bootstrapping module just bootstraps the system.
async / await here are operating on promises, so you essentially want to "start" the promise by calling .then or .catch.
My go-to snippet for this is creating an async run or main function, then attaching error handling to the process, something like this:
async function run() {
// run the app, you can await stuff in here
}
run().catch(err => {
console.error(err.stack)
process.exit(1)
})
In your case that would look like (Main.ts):
import System from "./System"
async function run() {
const system = new System()
await system.start()
}
run().catch(err => {
console.error(err.stack)
process.exit(1)
})
You don't need to export anything since this module file isn't being imported anywhere else (it's the entry file).
You can just call system.then() or system.catch(), but personally I like the async function run() pattern since you may need to coordinate more than one async thing in the future and this makes the code more explicit.
system.start().then() => {
value => export default value
}
In my opinion, a better way would be:
System.ts:
function System():Promise<string>{
//setup express and the server
return new Promise((res,rej) => {
//the server var is just the http server instance
server.listen(8000,() => resolve("server created"));
});
}
export {System}
And then in Main.ts:
import {System} from "yourpath"
And then:
System().then(() => {
//code runs when server is created
}).catch(err => console.error(err));
So no matter what I've read, even once I do it right, I can't seem to get the the hang of async and await. For example I have this in my startup.
startup.js
await CommandBus.GetInstance();
await Consumers.GetInstance();
Debugging jumps to the end of the get instance for CommandBus (starting up a channel for rabbitmq) and start Consumers.GetInstance() which fails since channel is null.
CommandBus.js
export default class CommandBus {
private static instance: CommandBus;
private channel: any;
private conn: Connection;
private constructor() {
this.init();
}
private async init() {
//Create connection to rabbitmq
console.log("Starting connection to rabbit.");
this.conn = await connect({
protocol: "amqp",
hostname: settings.RabbitIP,
port: settings.RabbitPort,
username: settings.RabbitUser,
password: settings.RabbitPwd,
vhost: "/"
});
console.log("connecting channel.");
this.channel = await this.conn.createChannel();
}
static async GetInstance(): Promise<CommandBus> {
if (!CommandBus.instance) {
CommandBus.instance = new CommandBus();
}
return CommandBus.instance;
}
public async AddConsumer(queue: Queues) {
await this.channel.assertQueue(queue);
this.channel.consume(queue, msg => {
this.Handle(msg, queue);
});
}
}
Consumers.js
export default class Consumers {
private cb: CommandBus;
private static instance: Consumers;
private constructor() {
this.init();
}
private async init() {
this.cb = await CommandBus.GetInstance();
await cb.AddConsumer(Queues.AuthResponseLogin);
}
static async GetInstance(): Promise<Consumers> {
if (!Consumers.instance) {
Consumers.instance = new Consumers();
}
return Consumers.instance;
}
}
Sorry I realize this is in Typescript, but I imagine that doesn't matter. The issue occurs specifically when calling cb.AddConsumer which can be found CommandBus.js. It tries to assert a queue against a channel that doesn't exist yet. What I don't understand, is looking at it. I feel like I've covered all the await areas, so that it should wait on channel creation. The CommandBus is always fetched as a singleton. I don't if this poses issues, but again it is one of those areas that I cover with awaits as well. Any help is great thanks everyone.
You can't really use asynchronous operations in a constructor. The problem is that the constructor needs to return your instance so it can't also return a promise that will tell the caller when it's done.
So, in your Consumers class, await new Consumers(); is not doing anything useful. new Consumers() returns a new instance of a Consumers object so when you await that it doesn't actually wait for anything. Remember that await does something useful with you await a promise. It doesn't have any special powers to await your constructor being done.
The usual way around this is to create a factory function (which can be a static in your design) that returns a promise that resolves to the new object.
Since you're also trying to make a singleton, you would cache the promise the first time you create it and always return the promise to the caller so the caller would always use .then() to get the finished instance. The first time they call it, they'd get a promise that was still pending, but later they'd get a promise that was already fulfilled. In either case, they just use .then() to get the instance.
I don't know TypeScript well enough to suggest to you the actual code for doing this, but hopefully you get the idea from the description. Turn GetInstance() into a factory function that returns a promise (that you cache) and have that promise resolve to your instance.
Something like this:
static async GetInstance(): Promise<Consumers> {
if (!Consumers.promise) {
let obj = new Consumers();
Consumers.promise = obj.init().then(() => obj);
}
return Consumers.promise;
}
Then, the caller would do:
Consumers.getInstance().then(consumer => {
// code here to use the singleton consumer object
}).catch(err => {
console.log("failed to get consumer object");
});
You will have to do the same thing in any class that has async operations involved in initializing the object (like CommandBus) too and each .init() call needs to call the base class super.init().then(...) so base class can do its thing to get properly initialized too and the promise your .init() is linked to the base class too. Or, if you're creating other objects that themselves have factory functions, then your .init() needs to call those factory functions and link their promises together so the .init() promise that is returned is linked to the other factory function promises too (so the promise your .init() returns will not resolve until all dependent objects are all done).
I have an ES6 class which I need to mock it's methods. Following the documentation i made a manual mock of this, and got the constructor to both be called and asserted.
My function that consumes this class is just a basic function that runs one of the class methods.
test.js
const mockConnect = jest.fn();
const mockAccess = jest.fn();
jest.mock('../../src/connection');
const connection = require('../../src/connection').default;
connection.mockImplementation(() => {
return {
connect: mockConnect,
access: mockAccess.mockReturnValue(true),
};
});
caller_function();
expect(connection).toHaveBeenCalled(); // works properly as the constructor is called
expect(connection).toHaveBeenCalledWith('something'); // works
expect(mockAccess).toHaveBeenCalled(); // says it was not called when it should have
caller_function.js
import connection from 'connection';
const conn = new connection('something');
export function caller_function() {
conn.access(); // returns undefined when mock says it should return true
}
This is happening because you're using mockImplementation() instead of a manual mock or the factory parameter to jest.mock(), and your mocked object is being created during the module loading process, since the constructor call is not inside of any function. What's happening is:
The call to jest.mock('../../src/connection') runs and sets connection to be an automatic mock.
The conn object is created using the automatic mock. Therefore its access method returns undefined.
The call to mockImplementation() happens, changing the connection mock. However, since the conn object has already been created, it doesn't get the custom implementation.
Moving the constructor call into caller_function is one way to fix it:
export function caller_function() {
const conn = new connection('something');
conn.access();
}
You could also use the factory parameter to jest.mock(), specifying the implementation there, instead of calling mockImplementation(). That way you won't have to change your implementation code:
const mockConnect = jest.fn();
const mockAccess = jest.fn();
import connection from '../../src/connection';
jest.mock('./so-import', () => {
return jest.fn().mockImplementation(() => {
return {
connect: mockConnect,
access: mockAccess.mockReturnValue(true)
};
});
});
...
BTW the convention for ES6 class names is to begin with an uppercase letter. I was temporarily confused by the lowercase name connection.
Did you try doing connection.mockClear(); before you write a mockImplementation for the methods?
Also please refer to this https://jestjs.io/docs/en/es6-class-mocks
I am using Microsoft's tslint-microsoft-contrib tslint configuration and I am really happy with it. However there is one rule which warns me about my code. I don't understand the rule description text or how I could solve this more elegant.
[tslint] Backbone get() called outside of owning model:
this.client.get('locations') (no-backbone-get-set-outside-model)
Code:
import * as Redis from 'ioredis';
import config from './config';
export class RedisWrapper {
private client: Redis.Redis
constructor(redisUrl: string) {
this.client = new Redis(redisUrl)
}
public async getLocations(): ILocation[] {
const locationsResponse: string = await this.client.get('locations')
}
}
In this line the tslint warning pops up: const locationsResponse: string = await this.client.get('locations')
The question:
Originally I faced this issue at a different place in my project and I thought I was supposed to write wrapper methods with typedefs, but I wasn't able to make tslint happy with that either. Can someone enlighten me what this rule means and how I could solve it?
I will quote HamletDRC (from the Microsoft team) who explained the rule itself very well:
The point of the no-backbone-get-set-outside-model rule is to make
sure that you don't invoke dynamically dispatched methods that the
compiler cannot enforce correctness on. For example, the compiler will
not complain if you type route.params.get('id'),
route.params.get('ID'), route.params.get('Id') but only one of those
invocations will actually work at runtime. The design advice is to
define a statically typed "getId(): number" method on the RouteParams
object so the compiler can enforce these calls. So, in my opinion the
rule actually has found an issue in your code that you should fix (but
see my second point :) )
Source: https://github.com/Microsoft/tslint-microsoft-contrib/issues/123
In this specific case one could extend the Redis class like this:
export class RedisWrapper extends Redis {
public async getLocations(): Promise<ILocation[]> {
const response: string = await this.get('locations');
if (response == null || response.length === 0) { return []; }
return <ILocation[]>JSON.parse(response);
}
}
I don't like anything I've seen so far...
Module
import lineReader from 'line-reader'
let bigFile = './huge-file.csv'
export default class DooDad {
constructor() {
this.dictionary = new Map()
// Something like this seems like it'd be nice...
// await this.load()
}
load() {
return new Promise((resolve, reject) => {
lineReader.eachLine(bigFile, (line, last) => {
// this.dictionary = The contents of huge-file.csv
})
})
}
doStuff(foo) {
// Uses this.dictionary to do something interesting
// Problem: Unusable without first calling and waiting for this.load()
}
}
Usage
import DooDad from '../doodad'
let doodad = new DooDad()
// We have to first call and wait for load() before doodad is useable
doodad.load().then(x => {
doodad.doStuff()
})
Seems like you'd either want to...
1) Make the loading synchronous
2) Make a static create function on DooDad that returns a promise that resolves to a new instance of a DooDad
3) Make the constructor return a Promise (seems weird) Asynchronous constructor
4) Emit an event when its done loading
5) Leave it how it is
6) ????
doodad.load().then() makes perfect sense to me. You don't want constructors to be async so it makes sense to have .load() be where the async stuff is.
The other pattern I've seen is you export a factory-type function that returns a promise and when that promise is resolved, the resolved value is your fully formed object. This has the advantage of there is no access to the object until the async stuff is done and there is no temptation by calling code to try to use it before it is ready.
import makeDooDad from '../doodad'
makeDooDad().then(doodad => {
// you only get access to the object here after it's been fully
// initialized
doodad.doStuff();
});
And, the makeDooDad() factory function does something like this inside of your module:
function makeDooDad() {
let d = new DooDad();
// fully initialize the doodad object before resolving the promise
// and before returning the object
return d.load().then(() => {
// let the object itself by the resolved value
return d;
});
}
As for your other options:
Make the loading synchronous
This could be OK only if it is only done at server startup time. There is generally no real cost to doing some synchronous I/O at server startup time and often it makes things a lot simpler. For example, require() itself does synchronous I/O.
Make a static create function on DooDad that returns a promise that resolves to a new instance of a DooDad
That is essentially what I recommended above with the factory function. This is often times a good option.
Make the constructor return a Promise (seems weird) Asynchronous constructor
No. Don't really want to do that. Constructors should return objects, not promises. Use a factory function to return a promise.
Emit an event when its done loading
There are other pieces of code that do this such as creating a writeStream, emits an open event on the stream when the stream is actually open. But, in the days of promises, this isn't my favorite way of doing things for other types of objects that aren't already using lots of events.
Leave it how it is
It's OK as is, but I prefer the factory function that returns a promise.