I need to instantiate an object asynchronously. But the constructor method cannot be declared async async constructor(){} and be called like so async new MyClass(). That would be strange anyway. Therefore, some kind of factory is needed. I also want to hide the constructor. This would be very easy to achieve if the constructor could be declared private: #constructor. But even with ES2022 private class features this is not possible. However, there are several patterns to solve these two problems. I will list 3 of them (in the context of ES6 modules).
I would like to know which pattern, if any, are considered good and which are not. Or if there are even better solutions, please let me know.
Method I: static boolean (without a factory)
let calledInit = false;
export default class MyClass {
constructor(data) {
if (calledInit === false)
throw Error('private constructor');
calledInit = false;
// member initializations here
}
static async initialize() {
calledInit = true;
// do some async stuff here
return new MyClass(data);
}
}
Method II: closure and singleton-factory
const MyClass = (function() {
class MyClass {
constructor(data) {
// member initializations here
}
}
return {
initialize: async function() {
// do some async stuff here
return new MyClass(data);
}
}
})();
Object.freeze(MyClass);
export default MyClass;
use them both like so:
const obj = await MyClass.initialize();
Method III: factory function
class MyClass {
constructor(data) {
// member initializations here
}
}
export default async function MyClassInitializer() {
// do some async stuff here
return new MyClass(data);
}
use it like so:
const obj = await MyClassInitializer();
Edit: To clarify, I believe hiding the constructor is beneficial. Mainly for debugging purpose. Let's say the constructor is public and someone else is instantiating the object using the new operator. It always fails with obscure error messages and she/he can't make sense of it. All this, because she/he didn't carefully read the docs which clearly states to use the initialize() method to create an instance. This could easily have been prevented by hiding the constructor in the first place.
Disclaimer This is my opinion
Instantiating an object asynchronously is not really a thing for a good reason.
The purpose of the constructor is to construct your object.
You should not put something like load operations from a file, API etc. in the constructor.
Put them into a separate function and use the result as an argument to your class.
async () => {
const data = await fetch(...) // get data / do some sideeffect
const myObject = new MyObject(data)
}
This is also usually desirable since you can update some state to track the progress of the request. Or you might want to cache it and use it in multiple places.
async () => {
state = "LOADING"
const data = await fetch(...) // get data / do some sideeffect
state = "SUCCESS"
const myObject = new MyObject(data)
}
Hiding the constructor is not really needed since you get an error when you do not pass data to it. And the user of your class should know what to pass. If you use typescript, this would be even more strict.
Sure you can put this in one initialize function as in method 3.
Related
There is an abstarct class Layer:
export abstract class Layer {
protected layers: any[] = [];
abstract get(): Promise<Layer[]>;
}
Concrete class:
export class CommonLayers extends Layer {
async get(): Promise<Layer[]> {
if (!hasRole('admin')) return;
/* items belows could be loaded from server, now it is empty array
this.layers.push({
title: "Title",
items: [],
count: 3,
});
return this.layers;
}
}
Using:
async getLayers(): Promise<any[]> {
// let thematicLayers = await new ThematicLayers();
let customLayers = await new CommonLayers();
return [...thematicLayers, ...customLayers];
}
I dislike this part of code:
this.layers.push({
title: "Title",
items: [],
count: 3,
});
return this.layers;
This method does a few things.
Checks if user has admin role
Gets data from response if there is
Prepares data
Sets data to this.layers
Returns this.layers
I think this method has a lot responsibilities. So when programmer calls the method get() he assumes it just returns data.
And it look not readable!
How to fix it?
You need a method that fetches the data from the server. That requires a check if the user is an admin (note this should really be happening on the back end, but whatever), and there is no reason not to return the fetched data. I don't see a problem here, as far as it goes.
The problem is that you are correct that using Array.prototype.push in an async function or .then callback is a code smell and you don't provide any way to get at the data other than to access the property synchronously (which will expose you to all sorts of timing bugs) or call the method that fetches from the server (unnecessary http calls).
There really isn't any way to square this circle, I would just say that you should stay async all the way:
class Foo {
// this should be a specific type rather than any
protected layers: Promise<any[]> = Promise.resolve([])
// I strongly recommend picking a method name other than 'get'
async get(): Promise<any[]> {
if (!hasRole('admin')) return [];
const response = await fetch(someURL);
this.layers = response.json();
return this.layers;
}
}
Playground
This is less convenient for callers, but there isn't necessarily a robust way around it.
What I would split out into a second (or more) method is if you do any logic on the frontend with the returned data. But if all you're doing is fetching it (conditional on being an admin) then I think it's fine.
I am trying to create a structure where i can stack multiple classes that extend the same parent class. by stacking I mean something like stacking then in the following example :
promise.then(
data=>{
return promise;
}
).then(
data=>{
return promise;
}
).then(
....
)
where the second datais the one resolved by the first returned promise.
for my case I have an operation class which have multiple child classes createUser, sendEmail and generateConfig. and I want to be able to stack those the following way :
let generateConfigREturnedValue = createUser(null, usercreationInput)
.sendEmail(createUSerReturnedValue, sendEmailInput)
.generateConfig(sendEmailReturnedValue, generateConfigInput)
.finish();
I want to be able to stack, the above way, any class that extends the operation parent class.
Note : The ReturnedValue arguments are not passed explicitly that way but rather that is how they will be passed and received by the nextInline operation.
EDIT:
So the parent operation class would have an abstract method called execute which will be automatically called wen chaining the child classes ( the child class will have its main code in that method ). So technically the class chaining I am talking about is chaining of the execute methods of each class while passing the arguments.
My thoughts :
Adding all the child operations to the prototype of each of the child operations, thus all child operations will have each other to call. After that I need to setup the, under the hood, argument passing from an operation to an other.
extending the prototype attribute lookup on the parent operation class to include fetching objects using their names from a dependency container ( something like awilix )
is what I want to do is implementable and which is the best thought of the couple above ?
First of all, if your functions perform asynchronous actions in order to get a result, you cannot expect your .finish() call to return that final result synchronously. The best you can hope for is a promise from .finish().
I would suggest to do all the hard work in the finish method, and let all other methods just collect the input data.
I assume you have the core asynchronous functions already available, and I will refer to them via the api object:
class Chain {
createUser(userCreationInput) {
this.userCreationInput = userCreationInput;
return this;
}
sendEmail: function(sendEmailInput) {
this.sendEmailInput = sendEmailInput;
return this;
}
generateConfig(generateConfigInput) {
this.generateConfigInput = generateConfigInput;
return this;
}
async finish() {
let createUserReturnedValue = await api.createUser(null, this.userCreationInput);
let sendEmailReturnedValue = await api.sendEmail(createUserReturnedValue, this.sendEmailInput);
return api.generateConfig(sendEmailReturnedValue, this.generateConfigInput);
}
}
let generateConfigReturnedValue = await new Chain()
.createUser(usercreationInput)
.sendEmail(sendEmailInput)
.generateConfig(generateConfigInput)
.finish();
Obviously the await should be used in an async function context.
This is just focussing on core functionality. You would have to add checks that all information is provided when finish is called, deal with error handling, ...etc.
If you don't like the new Chain() part in the expression, then you could define these three methods also as global functions and let those return a new instance of Chain. It's a choice between littering the global namespace or having the extra new Chain().
A middle ground would be to define the functions also as static methods -- again, returning a new instance of Chain. Then the expression would be:
let x = Chain.createUser(usercreationInput). // ...etc
I have a small npm package of utilities Utilities that I want to use across two of my codebases, AppA and AppB.
Utilities will expose a class with some functions, and it is AppA and AppB's job to subclass where needed.
class Utilities {
processData(data, callback) {
var result = this.validateData(data);
// This will fail for AppB, because validateData returns a promise.
var length = result.length;
callback(length);
}
validateData(data) {
// Must be subclassed
}
}
AppA:
class AppA extends Utilities {
validateData(data) {
return [1, 2, 3];
}
}
AppB:
class AppB extends Utilities {
validateData(data) {
// returns a Promise
return AsyncMethods.checkData(data);
}
}
The Utilities package is written in a synchronous manner, as is app A. AppB however requires interfacing with some async methods inside of validateData. My Utilities class however does not know that the consumer has an async API.
I can change both Utilities and AppB with ease, but not AppA. So I need a way to write Utilities in a way that can work in both sync and async environments.
Ideally, I'd just modify AppB and make it wait:
class AppB {
async validateData(data) {
var result = await AsyncMethods.checkData(data);
return result;
}
}
However, this still returns a Promise object and not the actual result, because it's an async function.
Another solution that falls just short of working is waiting inside processData in Utilities:
async processData(data, callback) {
var result = await this.validateData(data);
var length = result.length;
callback(length);
}
However, this breaks AppA, as its implementation is not async, and I cannot change AppA.
Do I have any options here? Perhaps a way to make all functions await by default?
Here's a fiddle: https://jsfiddle.net/1ptoczh7/3/
Notice for AppB the result is undefined. I need to be able to get both outputs without changing AppA.
I need a way to write Utilities in a way that can work in both sync and async environments.
Write two different utilities. You will almost surely end up with that in any case. Factor out common code into a shared module (have a look at this answer for how to effectively do that). Asynchronous functionality is just too different from synchronous functionality.
In your example, this would most likely look like this:
function getLength(result) {
return result.length;
}
class Utilities {
processData(data) { // returns int
return getLength(this.validateData(data));
}
// abstract validateData(data): Result
// to be overwritten in subclass
}
class AsyncUtilities {
processData(data) { // returns Promise<int>
return this.validateData(data).then(getLength);
}
// abstract validateData(data): Promise<Result>
// to be overwritten in subclass
}
i write this code but i don`t now why my console.log give me undefined and how can i get value of prototype properties or functions
(function() {
function Users() {}
Users.prototype.getUser = function() {
$.getJSON('/usersList/users.json', function(request) {
Users.prototype.allUsers = request.users;
});
}
var users = new Users();
users.getUsers();
console.log(users.allUsers);
}
())
What i wont to achieve is to have this user list as my object property like User.allUsers in some array.
Thanks
This is because $.getJSON is asynchronous.
When you create an object from User prototype, $.getJSON has to call its callback yet.
In the other hand, you're trying to simulate global variables adding data properties to a given prototype. This is a bad practice and, at the end of the day, you've already realized that you got stuck on this approach.
What you need an initialization code executed whenever the site or app is started so those allUsers are called once and injected everywhere:
const initApp = () => Promise.all([
$.getJSON("/someData/users.json")
// other comma-separated async functions which return promises
])
initApp().then(([allUsers, otherData, yetAnotherData]) => {
callSomeFunctionWhichRequiresAllUsers(allUsers)
})
I'm trying to create a constructor for a blogging platform and it has many async operations going on inside. These range from grabbing the posts from directories, parsing them, sending them through template engines, etc.
So my question is, would it be unwise to have my constructor function return a promise instead of an object of the function they called new against.
For instance:
var engine = new Engine({path: '/path/to/posts'}).then(function (eng) {
// allow user to interact with the newly created engine object inside 'then'
engine.showPostsOnOnePage();
});
Now, the user may also not supply a supplement Promise chain link:
var engine = new Engine({path: '/path/to/posts'});
// ERROR
// engine will not be available as an Engine object here
This could pose a problem as the user may be confused why engine is not available after construction.
The reason to use a Promise in the constructor makes sense. I want the entire blog to be functioning after the construction phase. However, it seems like a smell almost to not have access to the object immediately after calling new.
I have debated using something along the lines of engine.start().then() or engine.init() which would return the Promise instead. But those also seem smelly.
Edit: This is in a Node.js project.
Yes, it is a bad practise. A constructor should return an instance of its class, nothing else. It would mess up the new operator and inheritance otherwise.
Moreover, a constructor should only create and initialize a new instance. It should set up data structures and all instance-specific properties, but not execute any tasks. It should be a pure function without side effects if possible, with all the benefits that has.
What if I want to execute things from my constructor?
That should go in a method of your class. You want to mutate global state? Then call that procedure explicitly, not as a side effect of generating an object. This call can go right after the instantiation:
var engine = new Engine()
engine.displayPosts();
If that task is asynchronous, you can now easily return a promise for its results from the method, to easily wait until it is finished.
I would however not recommend this pattern when the method (asynchronously) mutates the instance and other methods depend on that, as that would lead to them being required to wait (become async even if they're actually synchronous) and you'd quickly have some internal queue management going on. Do not code instances to exist but be actually unusable.
What if I want to load data into my instance asynchronously?
Ask yourself: Do you actually need the instance without the data? Could you use it somehow?
If the answer to that is No, then you should not create it before you have the data. Make the data ifself a parameter to your constructor, instead of telling the constructor how to fetch the data (or passing a promise for the data).
Then, use a static method to load the data, from which you return a promise. Then chain a call that wraps the data in a new instance on that:
Engine.load({path: '/path/to/posts'}).then(function(posts) {
new Engine(posts).displayPosts();
});
This allows much greater flexibility in the ways to acquire the data, and simplifies the constructor a lot. Similarly, you might write static factory functions that return promises for Engine instances:
Engine.fromPosts = function(options) {
return ajax(options.path).then(Engine.parsePosts).then(function(posts) {
return new Engine(posts, options);
});
};
…
Engine.fromPosts({path: '/path/to/posts'}).then(function(engine) {
engine.registerWith(framework).then(function(framePage) {
engine.showPostsOn(framePage);
});
});
I encountered the same problem and came up with this simple solution.
Instead of returning a Promise from the constructor, put it in this._initialized property, like this:
function Engine(path) {
this._initialized = Promise.resolve()
.then(() => {
return doSomethingAsync(path)
})
.then((result) => {
this.resultOfAsyncOp = result
})
}
Then, wrap every method in a callback that runs after the initialization, like that:
Engine.prototype.showPostsOnPage = function () {
return this._initialized.then(() => {
// actual body of the method
})
}
How it looks from the API consumer perspective:
engine = new Engine({path: '/path/to/posts'})
engine.showPostsOnPage()
This works because you can register multiple callbacks to a promise and they run either after it resolves or, if it's already resolved, at the time of attaching the callback.
This is how mongoskin works, except it doesn't actually use promises.
Edit: Since I wrote that reply I've fallen in love with ES6/7 syntax so there's another example using that.
class Engine {
constructor(path) {
this._initialized = this._initialize(path)
}
async _initialize() {
// actual async constructor logic
this.resultOfAsyncOp = await doSomethingAsync(path)
}
async showPostsOnPage() {
await this._initialized
// actual body of the method
}
}
To avoid the separation of concerns, use a factory to create the object.
class Engine {
constructor(data) {
this.data = data;
}
static makeEngine(pathToData) {
return new Promise((resolve, reject) => {
getData(pathToData).then(data => {
resolve(new Engine(data))
}).catch(reject);
});
}
}
The return value from the constructor replaces the object that the new operator just produced, so returning a promise is not a good idea. Previously, an explicit return value from the constructor was used for the singleton pattern.
The better way in ECMAScript 2017 is to use a static methods: you have one process, which is the numerality of static.
Which method to be run on the new object after the constructor may be known only to the class itself. To encapsulate this inside the class, you can use process.nextTick or Promise.resolve, postponing further execution allowing for listeners to be added and other things in Process.launch, the invoker of the constructor.
Since almost all code executes inside of a Promise, errors will end up in Process.fatal
This basic idea can be modified to fit specific encapsulation needs.
class MyClass {
constructor(o) {
if (o == null) o = false
if (o.run) Promise.resolve()
.then(() => this.method())
.then(o.exit).catch(o.reject)
}
async method() {}
}
class Process {
static launch(construct) {
return new Promise(r => r(
new construct({run: true, exit: Process.exit, reject: Process.fatal})
)).catch(Process.fatal)
}
static exit() {
process.exit()
}
static fatal(e) {
console.error(e.message)
process.exit(1)
}
}
Process.launch(MyClass)
This is in typescript, but should be easily converted to ECMAscript
export class Cache {
private aPromise: Promise<X>;
private bPromise: Promise<Y>;
constructor() {
this.aPromise = new Promise(...);
this.bPromise = new Promise(...);
}
public async saveFile: Promise<DirectoryEntry> {
const aObject = await this.aPromise;
// ...
}
}
The general pattern is to store the promises as internal variables using the constructor and await for the promises in the methods and make the methods all return promises. This allows you to use async/await to avoid long promise chains.
The example I gave is good enough for short promises, but putting in something that requires a long promise chain will make this messy, so to avoid that create a private async method that will be called by the constructor.
export class Cache {
private aPromise: Promise<X>;
private bPromise: Promise<Y>;
constructor() {
this.aPromise = initAsync();
this.bPromise = new Promise(...);
}
public async saveFile: Promise<DirectoryEntry> {
const aObject = await this.aPromise;
// ...
}
private async initAsync() : Promise<X> {
// ...
}
}
Here is a more fleshed out example for Ionic/Angular
import { Injectable } from "#angular/core";
import { DirectoryEntry, File } from "#ionic-native/file/ngx";
#Injectable({
providedIn: "root"
})
export class Cache {
private imageCacheDirectoryPromise: Promise<DirectoryEntry>;
private pdfCacheDirectoryPromise: Promise<DirectoryEntry>;
constructor(
private file: File
) {
this.imageCacheDirectoryPromise = this.initDirectoryEntry("image-cache");
this.pdfCacheDirectoryPromise = this.initDirectoryEntry("pdf-cache");
}
private async initDirectoryEntry(cacheDirectoryName: string): Promise<DirectoryEntry> {
const cacheDirectoryEntry = await this.resolveLocalFileSystemDirectory(this.file.cacheDirectory);
return this.file.getDirectory(cacheDirectoryEntry as DirectoryEntry, cacheDirectoryName, { create: true })
}
private async resolveLocalFileSystemDirectory(path: string): Promise<DirectoryEntry> {
const entry = await this.file.resolveLocalFilesystemUrl(path);
if (!entry.isDirectory) {
throw new Error(`${path} is not a directory`)
} else {
return entry as DirectoryEntry;
}
}
public async imageCacheDirectory() {
return this.imageCacheDirectoryPromise;
}
public async pdfCacheDirectory() {
return this.pdfCacheDirectoryPromise;
}
}