4

I am trying to implement the Customer object in NodeJs and that the instance is able to collect its data.

class CustomerModel extends Model {
  public customer

  constructor(email:string) {
    super();
    this.collection = 'customer';
    this.customer = await CustomerLayer.getCustomerByEmail(email);
  }
}

But I can't have an asynchronous constructor. I have seen that in Javascript you can do the following:

const sleep = () => new Promise(resolve => setTimeout(resolve, 5000));
    class Example {
      constructor () {
        return new Promise(async (resolve, reject) => {
          try {
            await sleep(1000);
            this.value = 23;
          } catch (ex) {
            return reject(ex);
          }
          resolve(this);
        });
      }
    }

    (async () => {
      // It works as expected, as long as you use the await keyword.
      const example = await new Example();

      console.log(example instanceof Example);
      console.log(example.value);
    })();

But I think it is not correct to return the data from the constructor. Is there any correct way to call asynchronous methods from the constructor?

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • I think this is a mistake about async/await... – Olaf Erlandsen Jan 20 '20 at 20:42
  • In this situation, I usually declare an `init` method which I have to call manually after creating the instance. I find it much cleaner, and a constructor method is supposed to return an instance, not a promise – blex Jan 20 '20 at 20:45
  • 1
    something like this? https://stackoverflow.com/questions/43431550/async-await-class-constructor/43433773#answer-50885340 – karthick Jan 20 '20 at 20:46
  • [You don't](https://stackoverflow.com/q/24398699/1048572). – Bergi Jan 20 '20 at 21:00
  • Multiple options for [Asynchronous operations in a constructor](https://stackoverflow.com/questions/49905178/asynchronous-operations-in-constructor/49906064#49906064). – jfriend00 Jan 20 '20 at 23:40

2 Answers2

4

I wouldn't do it in constructor. Probably init method fits this use case better.

class CustomerModel extends Model {
  public customer

  constructor(email:string) {
    super();
    this.collection = 'customer';
  }
  async init() {
   this.customer = await CustomerLayer.getCustomerByEmail(email);
  }
}
const myClass = new CustomerModel();
await myClass.init();

You could also consider creating a static method to return the instance which internally create object and init.

Ashish Modi
  • 7,529
  • 2
  • 20
  • 35
1

It's not possible. You have a few options here:

  • You can explicitly return a Promise from the constructor (construct it via new Promise). This is what you're doing in your second code. await can always be substituted with the Promise constructor and .then. But, it's pretty weird, since one would always expect the result of new Example() to then be an instanceof Example - one wouldn't expect new Example() to result in a Promise. This is what your first code would look like using this method:
class CustomerModel extends Model {
  public customer

  constructor(email:string) {
    super();
    this.collection = 'customer';
    return CustomerLayer.getCustomerByEmail(email)
      .then((customerByEmailResolveValue) => {
        this.customerByEmailResolveValue = customerByEmailResolveValue;
        return this;
      });
  }
}
const customerProm = new CustomerModel('foo');
customerProm.then((actualCustomerInstance) => {
  // use actualCustomerInstance
});
  • In the constructor, assign the Promise to a property of the instance. Then, when you need to consume that value, call .then on the property:
class CustomerModel extends Model {
  public customer
  constructor(email:string) {
    super();
    this.collection = 'customer';
    this.customerProm = CustomerLayer.getCustomerByEmail(email);
  }
}
const customer = new CustomerModel('foo');
customer.customerProm.then((result) => {
  // use result;
});
  • You can also retrieve the value before creating the instance, then pass it in:
class CustomerModel extends Model {
  public customer
  constructor(email:string, customerByEmailResolveValue) {
    super();
    this.collection = 'customer';
    this.customer = customerByEmailResolveValue;
  }
}
CustomerLayer.getCustomerByEmail(email)
  .then((customerByEmailResolveValue) => {
    const customer = new CustomerModel('foo', customerByEmailResolveValue);
  });
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320