skip to Main Content

I came across some issues with async await when instantiating a class. I have a class user, and when instantiated I wish to automatically hash the password and assign it to the password.

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.hashPassword();
    }

    private async hashPassword(): Promise<void> {
        try {
            const salt = await bcrypt.genSalt(10);
            this.password = await bcrypt.hash(this.plainPassword, salt);
        } 
        catch (error) {
            throw error
        }
    }

The hash password returns nothing. But is a promise function, so when I do tests, I dont get any of the User keys… email, password, userId and username are undefined…

Can you help me?

2

Answers


  1. You’re not waiting for the asynchronous function to finish, and there’s no way to do so in a constructor (since a constructor can’t be async).

    If you really want hashing to occur asynchronously "in" the constructor, the password field would have to be a promise of a password that will get eventually fulfilled:

    async function hashPassword(
      password: string,
    ): Promise<string> {
      const salt = await bcrypt.genSalt(10);
      return bcrypt.hash(password, salt);
    }
    
    class User {
      public userId: string;
      public passwordHashPromise: Promise<string>;
    
      constructor(
        public userName: string,
        public plainPassword: string,
        public email: string,
      ) {
        this.userId = uuidv4();
        this.userName = userName;
        this.email = email.toLowerCase();
        this.passwordHashPromise = hashPassword(plainPassword);
      }
    }
    

    and to access it, you’d then do

    const user = new User("foo", "bar", "[email protected]");
    const passHash = await user.passwordHashPromise;
    
    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search