As @akx mentioned, having async constructors in JavaScript/TypeScript is impossible. However, we can circumvent this limitation by leveraging design patterns.
Let's explore two quick alternatives:
Builder pattern
import { v4 as uuidv4 } from 'uuid';
import bcrypt from 'bcryptjs';
class User {
public userId: string;
public password: string = '';
constructor(public userName: string, public plainPassword: string, public email: string) {
this.userId = uuidv4();
this.userName = userName;
this.email = email.toLowerCase();
this.plainPassword = plainPassword;
}
public async hashPassword(): Promise<void> {
try {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.plainPassword, salt);
} catch (error) {
throw error;
}
}
}
class UserBuilder {
private user?: User;
public initialize(userName: string, plainPassword: string, email: string): UserBuilder {
this.user = new User(userName, plainPassword, email);
return this;
}
public async build(): Promise<User|null> {
if (this.user) {
await this.user.hashPassword();
return this.user;
}
return null;
}
}
async function clientCode() {
const user = await new UserBuilder()
.initialize('John Doe', 'password', '[email protected]')
.build();
console.log(user);
}
clientCode();
Factory pattern
import { v4 as uuidv4 } from 'uuid';
import bcrypt from 'bcryptjs';
class User {
public userId: string;
public password: string = '';
private constructor(public userName: string, public plainPassword: string, public email: string) {
this.userId = uuidv4();
this.userName = userName;
this.email = email.toLowerCase();
this.plainPassword = plainPassword;
}
public async hashPassword(): Promise<void> {
try {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.plainPassword, salt);
} catch (error) {
throw error;
}
}
// factory method to create a User instance
public static async createUser(userName: string, plainPassword: string, email: string): Promise<User> {
const user = new User(userName, plainPassword, email);
await user.hashPassword();
return user;
}
}
// Using the factory method
async function clientCode() {
const user = await User.createUser('John Doe', 'password', '[email protected]');
console.log(user);
}
clientCode();
In essence, both patterns allow you to control the instantiation process, enhancing the readability and maintainability of your code. While these patterns may not offer a direct solution to the absence of async constructors in JavaScript/TypeScript, they effectively address the underlying problem.
hashSyncinstead ofhash. Ref: github.com/kelektiv/node.bcrypt.js#to-hash-a-password-1