3

I want to chain methods from a class. I have o problems with synchronous methods, but I don't know how to do it with asynchronous methods.

For example, this class:

class Example {

  constructor() {
    this.val = 0
  }

  async () {
    setTimeout(() => {
      this.val += 1
      return this
    }, 5000)
  }

  sync () {
    this.val += 1
    return this
  }

  check () {
    console.log('checker', this.val)
    return this
  }

}

This works:

new Example().sync().check()
> 1

But this doesn't work:

new Example().async().check()
> TypeError: Cannot read property 'check' of undefined

P.S. I want chaining, not Hell Callbacks.

ASGM
  • 11,051
  • 1
  • 32
  • 53
rdbmax
  • 41
  • 1
  • 1
  • 3
  • 3
    First of all, `async` is not ES6 but a proposal for ES7. – Bergi Sep 18 '15 at 15:30
  • 1
    You should be using [Promises](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise), not attempting to shift `this` references around. You'll not have that syntax still (until async/await arrive in ES7) but at least you can avoid callback hell – CodingIntrigue Sep 18 '15 at 15:32
  • As an aside, read [this](https://stackoverflow.com/a/591343/2039244) for a little info about the execution context of a function given to `setTimeout` and why doing `return this` doesn't work like you want it to. – sdgluck Sep 18 '15 at 15:34
  • You'd need to keep track internally whether the async method is done or not and have `check` monitor that. Btw, this has nothing to do with ES6 or classes (except promises are ES6, but they existed before that as well). – Felix Kling Sep 18 '15 at 15:35
  • 1
    If you want to chain async operations (like jQuery does with animations), then you can build a queue (or use promises) where each method can be stored until the previous async operation completes and can trigger the next operation and each async method and sync method that you want to work with this will have to notify the queue when they are done. – jfriend00 Sep 18 '15 at 15:35

4 Answers4

7

I expect that you want to call check() after the timeout has expired. The problem is that forks off, and you can't immediately have something available to return.

You could pass in check() as a callback:

class Example {

  constructor() {
    this.val = 0
  }

  async (callback) {
    setTimeout(() => {
      this.val += 1
      callback()
    }, 5000)
  }

  sync () {
    this.val += 1
    return this
  }

  check () {
    console.log('checker', this.val)
    return this
  }

}

// execution
var ex = new Example();
ex.async(ex.check)

... or a promise

class Example {

  constructor() {
    this.val = 0
  }

  async (callback) {
    var deferred = Q.defer()
    setTimeout(() => {
      this.val += 1
      deferred.resolve();
    }, 5000)
    return deferred.promise;
  }

  sync () {
    this.val += 1
    return this
  }

  check () {
    console.log('checker', this.val)
    return this
  }

}

// execution
var ex = new Example()
ex.async().then(() => ex.check())

... Or you could use ES6 generators

Jeff Fairley
  • 8,071
  • 7
  • 46
  • 55
  • `check()` is not a method call here. You should resolve with `this` and do `ex => ex.check()` – Bergi Sep 18 '15 at 15:46
  • Thanks you, i was looking for a way to chain asynchronous functions like that : new Example().async().sync().async().sunc() and so on .. But the only solutions are callbacks ( break the chain because next method is called in an anonymous functions ) and promises that breaks also the chain ( because next methods are also called inside an anonymous functions ).. So to "chain" asynchronous functions THE solution would be to queue methods ? – rdbmax Sep 18 '15 at 18:41
  • @rdbmax: the problem is that you cannot have really synchronous functions in a chain that involves asynchrony. You either can call synchronous methods from callbacks that are explicitly attached to the asynchronous actions, or you make all your methods asynchronous and put the (synchronous) actions on some queue managed in the instance state. – Bergi Sep 19 '15 at 10:42
  • I'm still waiting for the ES6 generator example. :-( – Noel Llevares Sep 26 '16 at 02:17
  • Sorry dashmug. I basically put that as a personal challenge to learn it, then got too busy. I'll remove the "example coming" bit. – Jeff Fairley Sep 26 '16 at 17:30
5

If all you want is new Example().async().check() to work, all you need to do is return this after you call setTimeout. Ex:

async () {
  setTimeout(() => {
    this.val += 1
  }, 5000)
  return this
}

The return this inside the timeout isn't necessary, since it'll be executing on its own. It's basically running stand alone at that point.

Really, if you want this whole thing to be running async completely, and you're able to control flow of when certain things occur, you need to be using promises to make that happen.

Christopher WJ Rueber
  • 2,141
  • 15
  • 22
4

If you are using async functions and methods, you are using promises (I suppose you know them, if not please learn about them before reading on).

You should not use setTimeout inside of an asynchronous function if you consider waiting for it. Instead, create a promise for the timeout, preferably using a helper function like this:

function timeout(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms)
    });
}

Now you can write your method like you'd really want to:

class Example {
  …
  async async () {
    await timeout(5000);
    this.val += 1;
    return this;
  }
  …
}

Of course, as an async function it does not return the instance itself, but a promise for it. If you are going to chain, you will have to call it inside of another asynchronous function where you can await that promise:

(async function() {
    (await (new Example().async())).check();
}());
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 2
    Oops, I assumed the OP was talking about [`async` methods](https://github.com/lukehoban/ecmascript-asyncawait) and used `async` as the keyword, not for a method name. I see now it's not what he meant, but I still think the answer is useful so I won't delete it. – Bergi Sep 18 '15 at 15:44
1

What you are looking for will be solved most elegantly by Promises. You'll need to likely install a polyfill, like Bluebird or q.

I would change your async method to:

async() {
    return new Promise((resolve, reject)=>{
        setTimeout(()=>{
            this.val += 1;
            resolve(this);
        }, 5000);
    });
}

And your calling code to:

new Example().async().then((instance)=>instance.check());

Unfortunately, until ES7 async functions are nailed down, there won't be an elegant way to do this without some form of callback.

Bryan Rayner
  • 4,172
  • 4
  • 26
  • 38