3

I've got a class that returns several methods to be chained like this:

class MyClass{
  foo(){
    if(condition) return this;
    else throw;
  }

  bar(){
    if(condition) return this;
    else throw;
  }
}

Conditions are certain checks that may be true or false depending on the internal state of the class

The idea is that I can execute several methods like this:

myInstance.foo().bar();

This way, if any of the conditions in any method fails, this statement will throw an error.

However, now I'm trying to check conditions that require a promise to be resolved:

class MyClass2{
  asynCheck(){
     myPromise.then((condition)=>{
        ... // returning a promise won't work
     });

I want to maintain the same syntax as before (adding an await/then), I don't mind changing foo and bar to be async as well, as long as the following syntax is valid:

await myInstance.foo().asyncCheck().bar();

Is there a way of doing this? Note that there may be more than one asyncCheck and may be called in any order

angrykoala
  • 3,774
  • 6
  • 30
  • 55
  • What's the problem? If `foo` and `bar` remain synchronous, and only last method of the chain is [possibly] async, it will work fine. Even if the last method is synchronous, `await` won't choke on it. – mpen Jun 14 '18 at 22:49
  • asyncCheck may be in the middle, and there are more than one async method. I'll edit that to clarify – angrykoala Jun 14 '18 at 22:52
  • It will have to be function that takes promise... or else you would have to catch before every check – Akxe Jun 14 '18 at 22:54
  • Or you Ming miss understood how promises work. Try returning promise inside of promise, you’ll see that it will chain seemlesly – Akxe Jun 14 '18 at 22:56
  • It won't chain, @Akxe , in the last example if a promise is returned in asyncCheck, it will try to execute the method bar of the promise returned, which won't exists – angrykoala Jun 14 '18 at 22:58
  • You will need to await each async function: `(await (await myInstance.foo().asyncCheck()).bar().asyncCheck()).foobar()`. I think you'll find it's more readable to just skip the chaining and do it in separate statements. – Paul Jun 14 '18 at 23:05
  • Possible duplicate of [Extending the built-in javascript Promise](https://stackoverflow.com/questions/43464308/extending-the-built-in-javascript-promise) – Evert Jun 15 '18 at 03:35
  • @Evert, extending a promise is part of one of the solutions to this problem, but it won't solve it by itself – angrykoala Jun 19 '18 at 11:16
  • @angrykoala, have you considered something like this: `async asynCheck() { if (await myPromise) return this; else throw; }`, then just: `(await myInstance.foo().asyncCheck()).bar()` ? – noseratio Dec 11 '18 at 02:36

3 Answers3

1

You can maybe do it with some major hacks...

class MyClass {
  foo() {
    const ret = new Promise((resolve, reject) => {
      resolve(1);
    })
    ret.bar = this.bar.bind(this);
    return ret;
  }

  bar() {
    const ret = new Promise((resolve, reject) => {
      resolve(2);
    })
    // hack `ret` to copy the methods over...
    return ret;
  }
}

(async() => {
  const c = new MyClass;
  console.log(await c.foo())
  console.log(await c.foo().bar())

})()
mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 1
    If you do this then `c.foo().bar()` will call `bar` ***before*** the Promise return by `c.foo()` is resolved. I don't think that is what the OP wants. You could adjust the hack to make it work though. Something like `ret.bar = async (...args) => { await ret; return this.bar.apply( args ); }` – Paul Jun 14 '18 at 23:13
  • @Paulpro Good point. I don't think it's doable then (without more awaits like you sugggested) – mpen Jun 14 '18 at 23:15
  • I think you can adjust the hack in your answer. I edited my above comment, but haven't thought it through too well. – Paul Jun 14 '18 at 23:16
  • I'm trying a hack that it is a bit like yours but the other way around, trying to extend MyClass with Promise – angrykoala Jun 14 '18 at 23:20
0

You could achieve this using an internal Promise field and reassigning it each time you call a method:

class MyClass {
  constructor(promise) {
    if (!promise) this.promise = Promise.resolve();
    else this.promise = promise;
  }
  foo(condition) {
    this.promise = this.promise.then(() => {
        console.log("foo");
        if (!condition) throw 'foo error';
    });
    return this;
  }
  bar(condition) {
    this.promise = this.promise.then(() => {
        console.log("bar");
        if (!condition) throw 'bar error';
    });
    return this;
  }
  asynCheck(conditionPromise) {
    this.promise = this.promise.then(() => {
        return conditionPromise.then(condition => {
          console.log("asynCheck");
          if (!condition) throw 'asynCheck error';
        });
    });
    return this;
  }
  catch(callback) {
    this.promise.catch(callback);
  }
}

let instance = new MyClass();

instance.foo(true)
        .asynCheck(new Promise((res, rej) => setTimeout(() => res(true), 2000)))
        .bar(true)
        .asynCheck(new Promise((res, rej) => setTimeout(() => res(false), 3000)))
        .foo(false)
        .catch(e => console.log(e));
Kirill Simonov
  • 8,257
  • 3
  • 18
  • 42
0

After a lot of testing, I found a possible solution extending the class with Promise, ending up with something like this:

class MyClass extends Promise {
  constructor(executor){
    super((resolve, reject)=>{return executor(resolve,reject)});
  }
  foo(){
    return new MyClass((resolve, reject)=>{
      this.then(()=>{
        if(condition) resolve();
        else reject();
      }).catch(()=> reject());
    })
  }

  asyncCheck(){
    return new MyClass((resolve, reject)=>{
      this.then(()=>{
         asyncCondition.then((condition)=>{
             if(condition) resolve();
             else reject();
         });
      }).catch(()=> reject());
    })
  }
}
const bar=new MyClass((r)=>{r()});
await bar.foo().asyncCheck();

The main tradeoff is that the constructor requires a dummy callback due to how extending promises work.

angrykoala
  • 3,774
  • 6
  • 30
  • 55