Search code examples
javascriptnode.jspromisees6-promise

Synchronous code in constructor with Promises


I am working on a project in Node - a language with which I have little familiarity.

In the project, I have a class that will be responsible for reading and writing data to a database - in this case, LevelDB. Ideally, I'd like to set-up the database connection in the constructor synchronously so that methods (writeItem, readItem, etc.) don't fail if they're called too fast. Or in other words, I don't want the constructor to return and allow the next line of code to run until all the promises are fulfilled.

I think I am either missing something fundamental to the language or there is some design pattern in node that I'm not aware of. A toy example that fails in the same way is here:

class AClass {
  constructor(n) {
    this.name = n;
    console.log('calling init.');
    this.init();
    console.log('init returned.');
  }

  func() {
    return new Promise(resolve =>  {
      setTimeout(() => {
        resolve(true);
      }, 2000);
    }); 
  }

  async init() {
    console.log('calling func()');
    let x = await this.func();
    console.log('after func(): ');
  }

}


let x = new AClass('test');
console.log(JSON.stringify(x));

This produces the output:

calling init.
calling func()
init returned.
{"name":"test"}
after func():

This is surprising to me. I would have expected:

calling init.
calling func()
after func():
init returned.
{"name":"test"}

The final objective is to instantiate a class that connects to a levelDB instance and does not return the object until that connection is made. So the code might look something like this:

let storage = new StorageObject();
storage.addItem(key, value);   // <-- this fails unless StorageObject
                               //     finishes the db setup.

Thanks! Sam


Solution

  • Your objective of not returning the instance until after the connection won't work (at least not like this). The constructor's job is to create an instance and return that. Functions need to return something synchronously. If it's performing some async operation then a function can return a promise instead, but you don't want a promise from the constructor — you need the instance.

    The easy way to do this is to require your object to be initialized after it's created, then the constructor can construct and return the instance and the init function is free to return a promise:

    class AClass {
      constructor(n) {/* some setup */}
    
      func() {
        return new Promise(resolve =>  {
          setTimeout(() => {
            resolve("some name");
          }, 1000);
        }); 
      }
    
      async init() {
        this.name = await this.func();
        return this
      }
    
    }
    
    new AClass('test').init()
    .then((initialized_obj) => console.log(initialized_obj))

    If you're doing this in node, you could also use eventEmitters to emit an event when the instance has been initialized. Something like:

    const EventEmitter = require('events');
    
    class AClass extends EventEmitter{
      constructor(n) {
        super()
        this.init()
        .then(() => this.emit("initialized"))
      }
    
      func() {
        return new Promise(resolve =>  {
          setTimeout(() => {
            resolve("some name");
          }, 1000);
        }); 
      }
    
      async init() {
        this.name = await this.func();
        return this
      }
    
    }
    
    let x = new AClass('test')
    x.on('initialized', () => console.log(x.name))