Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asynchronous operations in constructor

Hey I have question about prototype and inheretience in functions. Could you explan me how I can return arr from constructor and add this arr to prototype?

var example = new Constructor()
function Constructor(){
   Service.getService().then(function(data){
      this.arr = data.data.array;
      return this.arr
   })
}

Constructor.prototype.getArray = function(){
   console.log(this.arr)
})
example.getArray();

And in getArray this.arr is undefined. Service and getService() are angular factory and connection between front and back-end

like image 730
Mat.Now Avatar asked Apr 18 '18 16:04

Mat.Now


1 Answers

It is particularly difficult to put asynchronous operations in a constructor. This is for several reasons:

  1. The constructor needs to return the newly created object so it can't return a promise that would tell you when the async operation is done.
  2. If you do an asynchronous operation inside the constructor that sets some instance data and the constructor returns the object, then you have no way for the calling code to know when the async operation is actually done.

For these reasons, you usually don't want to do an async operation inside a constructor. IMO, the cleanest architecture below is the factory function that returns a promise that resolves to your finished object. You can do as much asynchronous stuff as you want in the factory function (call any methods on the object) and you don't expose the object to the caller until it is fully formed.

These are some of the various options for dealing with that issue:

Use Factory Function that Returns a Promise

This uses a factory function that does some of the more common work for you. It also doesn't reveal the new object until its fully initialized which is a good programming practice as the caller can't accidentally try to use a partially formed object in which the asynchronous stuff hasn't finished yet. The factory function option also cleanly propagates errors (either synchronous or asynchronous) by rejecting the returned promise:

// don't make this class definition public so the constructor is not public
class MyObj() {
   constructor(someValue) {
       this.someProp = someValue;
   }
   init() {
       return Service.getService().then(val => {
          this.asyncProp = val;
          return this;
       });
   }
}

function createMyObj(someValue) {
    let x = new MyObj(someVal);
    return x.init();
}

createMyObj(someVal).then(obj => {
    // obj ready to use and fully initialized here
}).catch(err => {
    // handle error here
});

If you're using modules, you can export only the factory function (no need to export the class itself) and thus enforce that the object is initialized properly and not used until that initialization is done.

Break async object initialization into a separate method that can return a promise

class MyObj() {
   constructor(someValue) {
       this.someProp = someValue;
   }
   init() {
       return Service.getService().then(val => {
          this.asyncProp = val;
       });
   }
}

let x = new MyObj(someVal);
x.init().then(() => {
    // ready to use x here
}).catch(err => {
    // handle error
});

Use Events to Signal Completion

This scheme is used in a lot of I/O related APIs. The general idea is that you return an object from the constructor, but the caller knows that object hasn't really completed its initialization until a particular event occurs.

// object inherits from EventEmitter
class MyObj extends EventEmitter () {
   constructor(someValue) {
       this.someProp = someValue;

       Service.getService().then(val => {
          this.asyncProp = val;
          // signal to caller that object has finished initializing
          this.emit('init', val);
       });
   }
}

let x = new MyObj(someVal);
x.on('init', () => {
    // object is fully initialized now
}).on('error', () => {
    // some error occurred
});

Hackish way to put the Async Operation in the Constructor

Though I wouldn't recommend using this technique, this is what it would take to put the async operation in the actual constructor itself:

class MyObj() {
   constructor(someValue) {
       this.someProp = someValue;
       this.initPromise = Service.getService().then(val => {
          this.asyncProp = val;
       });
   }
}

let x = new MyObj(someVal);
x.initPromise.then(() => {
   // object ready to use now
}).catch(err => {
   // error here
});

Note, you see the first design pattern in many places in various APIs. For example, for a socket connection in node.js, you would see this:

let socket = new net.Socket(...);
socket.connect(port, host, listenerCallback);

The socket is created in the first step, but then connected to something in the second step. And, then the same library has a factory function net.createConnection() which combines those two steps into one function (an illustration of the second design pattern above). The net module examples don't happen to use promises (very few nodejs original apis do), but they accomplish the same logic using callbacks and events.


Other note on your code

You likely also have an issue with the value of this in your code. A .then() handler does not naturally preserve the value of this from the surrounding environment if you pass it a regular function() {} reference. So, in this:

function Constructor(){
   Service.getService().then(function(data){
      this.arr = data.data.array;
      return this.arr
   })
}

The value of this when you try to do this.arr = data.data.array; is not going to be correct. The simplest way to fix that issue in ES6 is to use a fat arrow function instead:

function Constructor(){
   Service.getService().then(data => {
      this.arr = data.data.array;
      return this.arr
   });
}
like image 151
jfriend00 Avatar answered Nov 14 '22 23:11

jfriend00