How to chain async methods in javascript ES6 class

2019-05-29 19:31发布

问题:

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.

回答1:

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



回答2:

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.



回答3:

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();
}());


回答4:

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.