4

I would like to be able to .then() a instantiated object while following the Promise standards.

Or is this not recommended?

I tried the following but I don't think it's the right approach...

class MyClass extends Promise {
  constructor(){
    this.loaded = false;
    //Non promise third-party callback async function 
    someAsyncFunction( result => {
      this.loaded = true;
      this.resolve(result);
    }
  }
}

const myClass = new MyClass();
myClass.then( result => {
  console.log(result);
  console.log(myClass.loaded);
  // >>true
})

Edit:

What I ended up doing was the following, but I'm not sure about using .load().then()

class MyClass {
  constructor(){
    this.loaded = false;
  }
  load(){
    return new Promise( resolve => {
      //Non promise third-party callback async function 
      someAsyncFunction( result => {
        this.loaded = true;
        resolve(result);
      }
    })
  }
}

const myClass = new MyClass();
myClass.load().then( result => {
  console.log(result);
  console.log(myClass.loaded);
  // >>true
})
Mojimi
  • 2,561
  • 9
  • 52
  • 116
  • Why do you need `MyClass` here, if you are not really interested in an object of the instance `MyClass`? The given code could be solved using a function and closures. – t.niese Nov 30 '18 at 12:26
  • @t.niese well I am, just not in this particular piece of the code, it's a useless example just to show the syntax I was looking for – Mojimi Nov 30 '18 at 12:27
  • @Mojimi Side question but why are you extending `Promise`? What is `MyClass` supposed to do in your system? Also, [don't get bitten by inheritance](https://en.wikipedia.org/wiki/Composition_over_inheritance). – nicholaswmin Nov 30 '18 at 12:30
  • You can have custom `then` able objects, but your intend is not quite clear. If the code should ensure that the instance of `MyClass` is ready before you use it, then you should use either a factory function returning that object as soon as it is ready, or if certain function depend on async loading make those functions async too. The then able object does not prevent you from using before it was resolved, so that design does not help you with maintainability or error safty. – t.niese Nov 30 '18 at 12:31
  • @NicholasKyriakides as I mentioned in the comment of your answer, MyClass requires some async resources to be considered instantiated, so I wanted it to have a .then() method while following the Promise standards, that's why I thought extending Promise was the right approach since then it would also be a Promise – Mojimi Nov 30 '18 at 12:32
  • What does it do though? Is it a `Car` class? Let's assume it is, does it sound right to you to ever say `car.then()`? No it doesn't but how about `car.init().then()..`? – nicholaswmin Nov 30 '18 at 12:38
  • @NicholasKyriakides it's a FeatureLayer class that takes a service url as parameter, but isn't car.init().then() a bit... ugly? Or is that not a consideration – Mojimi Nov 30 '18 at 12:40
  • 2
    The `myClass.load().then()` and the `(new MyClass()).then()` are - with regard to maintainability and error safety - identical. In both cases you have to ensure that the one using the code knows `myClass` must not be used until the `then` is called, and that is a bad approach. The `.load()`/`init()` might be even more worse because the person needs to know that `new` does not init the object. – t.niese Nov 30 '18 at 12:42
  • @t.niese Well I don't know how else to design it, the object is only considered "loaded" through async resources loading, this is how all of Esri's ArcGIS for Javascript objects work, all of them have a .then() method since they are based upon some rest service, my design is based on what I've learned using their API. You can use the object on instantiating, it's just not loaded yet. – Mojimi Nov 30 '18 at 12:49
  • does the first one actually work? – Jonas Wilms Nov 30 '18 at 14:31
  • Potentially a duplicate [Async/Await Class Constructor](https://stackoverflow.com/q/43431550/218196) – Felix Kling Nov 30 '18 at 18:43

3 Answers3

2

Or is this not recommended?

Not only is this not recommended but it will also never work.

You should only use the constructor to define initial state values or perform construction value validations etc.

You can use an init() method to do what you want like so:

class MyClass {
  constructor(){
    this.loaded = false
  }

  init() {
    return someAsyncFunction()
      .then(value => {
        this.loaded = true

        return value
      })
  }
}
nicholaswmin
  • 21,686
  • 15
  • 91
  • 167
  • I'm guessing extending promise is no longer needed then? – Mojimi Nov 30 '18 at 12:23
  • I changed my question a little to show that the inner callback is not a promise, but your answer still is correct – Mojimi Nov 30 '18 at 12:23
  • @t.niese Copypasta typo. – nicholaswmin Nov 30 '18 at 12:23
  • @Mojimi It doesn't matter what async style the inner callback uses. Correct me If I'm wrong but I think the question basically boils down "Can I have an async constructor?". – nicholaswmin Nov 30 '18 at 12:25
  • I just wanted to .then() the instance while following the promise standard, that's why I thought I needed to extend promise because then the returned object would also be a promise but I got lost in the train of thought – Mojimi Nov 30 '18 at 12:26
  • Actually I might be wrong. What you're trying to do might work (with some modifications) but it's really bad design. See t.niese's comment on your OP. Since you're not returning the `Promise` in your constructor, which is what I thought you intended to do, my answer is pretty much moot. Ignore. – nicholaswmin Nov 30 '18 at 12:45
2

You can have custom then-able objects, but your intent is not quite clear. If the code should ensure that the instance of MyClass is ready before you use it, then you should use either a factory function returning that object as soon as it is ready, or if certain functions depend on async loading make those functions async too.

The then-able object does not prevent you from using before it was resolved, so that design does not help you with maintainability or error safety.

Factory function:

function createMyClass(options) {
     const myClass = new MyClass();
     return loadData(options).then( (result) => {
         myClass.loaded = true;
         myClass.result = result;

         return myClass;
     }) 
}


createMyClass({/*some options*/}).then( myClass => {
  console.log(myClass.result);
  console.log(myClass.loaded);
})

Load the result on demand:

class MyClass {
  constructor(options) {
    this.loaded = false;
    this.options = options;
  }

  result() {
    // only request the data if it was not already requested
    if (!this._result) {
      this._result = loadData(this.options).then(result => {
        this.loaded = true
        return result
      });
    }
    return this._result
  }
}


var myClass = new MyClass({/*....*/})

myClass.result().then(result => {
   console.log(result)
})

// could be called another time, and the data is not requested over again,
// as the Promise is reused
myClass.result().then(result => {
   console.log(result)
})
John-Luke Laue
  • 3,736
  • 3
  • 32
  • 60
t.niese
  • 39,256
  • 9
  • 74
  • 101
  • So, considering everything is a module loaded on demand, would I have a loader module/class that works as a factory for all other modules/classes? – Mojimi Nov 30 '18 at 13:04
  • @Mojimi That actually heavily depends on on your over all code structure. Form your shown code I doubt that you need an actual class at all, because you only use `result`, and `loaded` is not really required, because that information is already hold by the state of the Promise. – t.niese Nov 30 '18 at 13:08
-1

This is how you can write the promise

const someAsyncFunction = (parameters) => {
  return new Promise((resolve, reject) => {
    if (success) {
      resolve();
    } else {
      reject();
    }
  });
};
someAsyncFunction
 .then((result) => {

  })
 .catch((err) => {
  });
Hemil Patel
  • 311
  • 1
  • 8