6

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

jfriend00
  • 683,504
  • 96
  • 985
  • 979
Mat.Now
  • 1,715
  • 3
  • 16
  • 31
  • 2
    use `.bind` on your promise callback or use an arrow function. – Daniel A. White Apr 18 '18 at 17:02
  • 2
    you might also have to wait for the promise to resolve before the value can be there. – Daniel A. White Apr 18 '18 at 17:02
  • You call `example.getArray()` immediately but `getService()` might not have resolved and assigned `this.arr` yet... – Aaron Beall Apr 18 '18 at 17:03
  • it would be more preferable to put the promise request in `getArray` , in other to avoid the above problem by @Aaron – 0.sh Apr 18 '18 at 17:07
  • Thanks, I used .bind(this) and in getArray() I see arr but if I try display this.arr I get undefined – Mat.Now Apr 18 '18 at 17:32
  • Thanks, I used .bind(this) and in getArray() I see arr but if I try display this.arr I get undefined – Mat.Now Apr 18 '18 at 17:32
  • Uhhh, you can't return an async value from a constructor. It just can't be done. The constructor returns synchronously so your value is simply not available yet when the function returns. And, setting any instance data in an async operation inside a constructor leaves you with no ability to know when the async operation has actually done its job. – jfriend00 Apr 18 '18 at 17:43
  • Don't perform async stuff on construction. Keep the constructor wholly synchronous and call instances' asynchronous method(s) as required. If you want to cache anything, then cache promise(s), not data. – Roamer-1888 Apr 18 '18 at 17:55
  • [Don't put asynchronous stuff in your constructor](https://stackoverflow.com/q/24398699/1048572). – Bergi Apr 18 '18 at 19:41

1 Answers1

13

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
   });
}
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thank you very much for explanation. Now finally I understand how it work. btw. This knowledge junior should know? Beacause I have started learn javascript one year ago, and I dont't know where I am hehe . – Mat.Now Apr 18 '18 at 18:05
  • @Mat.Now - Always more stuff to learn. I'd say that REALLY understand asynchronous operations in Javascript is how you graduate from beginner to a much higher level in Javascript development. Understanding how the timing works and what the design tools are for dealing with that timing is a big deal in advancing to a higher level. This is a very, very common topic here on stack overflow (a common issue for beginners or intermediate developers) so there are lots of questions related to it. Follow these types of questions here to learn, then try to answer some yourself. – jfriend00 Apr 18 '18 at 18:09
  • Added eventEmitter design pattern. – jfriend00 Apr 18 '18 at 18:28
  • I think `initPromise` is a much better idea than the event listener. With promises, you can use `.then()` at any time, instead of having to check whether the `init` event has already fired or not (which only leads towards zalgo). – Bergi Apr 18 '18 at 19:46
  • If you are using a factory function approach (could also be a `static` method), you probably should just add another parameter to the constructor for the `.asyncProp`, and avoid the `init` method. – Bergi Apr 18 '18 at 19:47
  • @Bergi - Except I like to not even export the class itself because then exporting only the factory function allows you to make sure nobody misuses the class by trying to use it before it's been initialized. That's the advantage of the separate factory function option IMO that is not a static method of the class. – jfriend00 Apr 18 '18 at 19:59
  • @Bergi - I put the event listener option in because lots of existing APIs essentially use something like that (sockets, streams, etc...). If you want to know when it's done initializing, you get that info from an event listener. I wouldn't normally use it myself for the OP's type of example. – jfriend00 Apr 18 '18 at 20:00
  • Events become problematic when you extend the class. In the child you have to call super() on instantiation to inherit the parent methods, but if the parent already emits the event, your new code in the child constructor is executed after the super has already called the initReady event. –  Apr 14 '21 at 09:07
  • @MarkusRenéEinicher - if the whole reason you're using events is to signal completion of an asynchronous operation, that will always complete and fire the event after the child class is initialized because async operations signal completion through the event queue. – jfriend00 Apr 16 '21 at 02:34
  • I dont understand. Event is called when operation is completed. does not matter where the code is placed - parent or child. faster one wins. –  Apr 16 '21 at 08:31
  • @MarkusRenéEinicher - Do you understand the asynchronous operations ALWAYS complete on some future tick of the event loop AFTER the current sequence of code finishes executing? Thus, if an event is fired when some asynchronous operation started in the constructor finishes, then that event will always occur after all constructor code (parent and child) is done. Asynchronous completion gets in line in the event queue after things that are already running. – jfriend00 Apr 17 '21 at 02:35