96

What can ES6 Classes provide, as a pattern of organization, to asynchronous code. Below is an example with ES7 async/await, can an ES6-class have an asynchronous method, or constructor in ES7?

Can I do:

class Foo {
    async constructor() {
        let res = await getHTML();
        this.res = res
    }
}

And, if not how should a constructor work that does this?

class Foo {
    constructor() {
        getHTML().then( function (res) {
            this.res = res
        }
    }
}

If neither of these patterns work, can a constructor (and moreover classes) in an ES6 class support any form of asynchronicity that operates on the object's state? Or, are they only for purely synchronous code bases? The above examples are in the constructor, but they don't need to be.. Pushing the problem down one more level..

class Foo {
    myMethod () {
      /* Can I do anything async here */
    }
}

Or, with a getter...

class Foo {
    get myProp() {
        /* Is there any case that this is usefully asynchronous */
    }
}

The only examples I could think of is to run something in parallel inside of the same method/constructor/getter, but to have the whole thing resolve before conclusion. I'm just confused because it seems with all the push to fully asynchronous libraries, this just serves to confuse things. Except for textbook examples, I can't find one application they're useful for.

Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
  • you *could* return a promise from the constructor that resolves with the instance thus giving you access to the instance once it is initialized. – Kevin B May 31 '16 at 21:55
  • @KevinB the thought did occur to me, but it sounds horrible. – Evan Carroll May 31 '16 at 21:56
  • I don't particularly like it either, but... what other way would there be? there has to be a callback somewhere, it's either going to be a promise or a callback passed as a param. async/await still has a callback, you just don't see it. – Kevin B May 31 '16 at 21:57
  • @KevinB I think you're right, I think Classes are just a soon-to-be anti-pattern. Getting into them with an asychronous code base is going to be *very* difficult, getting out of them to write really useful asynchronous libraries is going to be *very* difficult, and from the prospective of the caller it's going to be supremely awkward to write, `await new Foo(url); ` I've made the question more broad, and I don't want to suppose I know the answer. Let's wait and see if anyone drums up anything else. If not, I'll bounty it. – Evan Carroll May 31 '16 at 22:02

5 Answers5

76

Can I do async constructor()

No, that's a syntax error - just like constructor* (). A constructor is a method that doesn't return anything (no promise, no generator), it only initialises the instance.

And, if not how should a constructor work that does this

Such a constructor should not exist at all, see Is it bad practice to have a constructor function return a Promise?

Can ES6 classes support any form of asynchrony that operates on the object's state? Or, are they only for purely synchronous code bases?

Yes, you can use asynchronous methods (even with the proposed async syntax) on classes, and getters can return promises as well.

However, you will need to decide what should happen when a method is called while some asynchronous process is still active. If you want it to sequence all your operations, you should store your instance's state inside a promise for the end of that sequence that you can chain onto. Or, if you want to allow parallel operations, the best approach is to make your instances immutable and return a promise for another instance.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 2
    This is a really good answer, I could edit it to provide an example of a class that chains onto itself through promises. I'm still not satisfied that this is ever a good idea. It seems more like an anti-pattern, still. – Evan Carroll May 31 '16 at 22:56
  • 5
    @EvanCarroll: Whether it is a good idea depends on what you are doing. ES6 `class` syntax doesn't bring anything new on the table, asynchrony and state in objects (instances) always has been complicated. – Bergi May 31 '16 at 23:08
  • 1
    "A constructor is a method that doesn't return anything (no promise, no generator), it only initialises the instance.": Not exactly true. A constructor in ES6 can return whatever object it likes. consider this: `class Foo { constructor(input) { return new Promise(resolve => resolve(input)); } } new Foo('bar').then(res => console.log(res)) ` – Chen Eshchar Sep 05 '18 at 08:18
  • 1
    @ChenEshchar Yes, you can do horrible stuff in constructors, but [you shouldn't](https://stackoverflow.com/q/24398699/1048572). Maybe I should have written "*…is a method whose job is to…*". – Bergi Sep 05 '18 at 08:22
31

Another way that Classes can be useful for arranging asynchronous tasks is with the exclusive use of static methods.

class Organizer {
    static async foo() {
        const data = await this.bar();
        data.key = value;
        return data;
    }
    static async bar() {
        return {foo:1, bar:2}
    }
};

Organizer.foo();

Of course, this is no different than creating a simple object literal, or a new file and including it, except you can more cleanly extend it.

Rimian
  • 36,864
  • 16
  • 117
  • 117
Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
  • 6
    You never should do that imo. Just use an object literal (you can extend those quite trivially as well using `Object.create` and `Object.assign`). Or, given ES6 modules, just use multiple named exports - extending those is even easier, it's like parasitic inheritance. – Bergi Jun 01 '16 at 00:53
  • 5
    never.. unless you also want to create instances of Organizer – neaumusic Jul 17 '18 at 17:57
18

ECMAScript 2017 is intended to be classes of async methods.

Invoking another async or promise-returning function is a one-liner!

The highly expressive code reads without interruption top to bottom regardless of deferred execution

If you have callbacks, alternative error handlers, parallel execution or other unmet needs, instantiate promises in function body. It is better to have code in the function body rather than in a promise executor, and note that there is no try-catch wrapping callback code: do next-to-nothing there.

The async method can return a promise, a regular value, or throw

The callback apis that Node.js people used to love, we will now hate with a passion: they must all be wrapped in promises

The beauty of async/await is that errors bubble up implicitly

class MyClass {
  async doEverything() {
    const sumOfItAll = await http.scrapeTheInternet() +
      await new Promise((resolve, reject) =>
        http.asyncCallback((e, result) => !e ? resolve(result) : reject(e)))
    return this.resp = sumOfItAll
  }
}

If limited to ECMAScript 2015 and no async, return promise values:

class ES2015 {
  fetch(url) {
    return new Promise((resolve, reject) =>
      http.get(url, resolve).on('error', reject))
      .then(resp => this.resp = resp) // plain ECMAScript stores result
      .catch(e => { // optional internal error handler
        console.error(e.message)
        throw e // if errors should propagate
      })
  }
}

This ECMAScript 2015 version is what you are really asking about, any desired behavior can be coded up using the returned promise construct.

If you really, really want to execute promises in the constructor, it is a good idea to pass in then-catch functions or provide some callback construct so that consumers can take action on promise fulfillment or rejection. In the constructor, it is also good practice to wait for nextTick/.then before doing real work.

Every promise needs a final catch or there will be trouble

Harald Rudell
  • 787
  • 6
  • 7
8

A workaround for not being able to add "async" to the constructor. When returning an async function it's like returning a promise so the constructor itself doesn't have to be async.

class Foo {
  constructor() {
    return this.init()
  }
  async init() {
    this.res = await getHTML()
    return this
  }
}
const foo = await new Foo()

even shorter but requires using a promise

class Foo {
  constructor() {
    return new Promise(async resolve => {
      this.res = await getHTML()
      resolve(this)
    })
  }
}
const foo = await new Foo()
Pawel
  • 16,093
  • 5
  • 70
  • 73
2

This is a late response, but the reason your second example doesn't work is because of a context error. When you pass a function () {} as an argument to Promise.prototype.then(), the lexical this inside the function will be the function itself, and not the class. This is why setting this.res seems to do nothing: this, in that case, refers to the function's own scope.

There are several ways of accessing an outer scope in Javascript, the classic one (that you see abundantly in ES5 code) being:

class Foo {
  constructor() {
    var _this = this

    getHTML().then(function (res) {
      _this.res = res
    })
  }
}

By making a reference to the class this, you can access it in inner scopes.

The ES6 way of doing it is to use arrow functions, which do not create a new scope, but rather "keep" the current one.

class Foo {
  constructor() {
    getHTML().then(res => this.res = res)
  }
}

Aside from context concerns, this is still not an optimal asynchronous pattern in my opinion, because you've got no way to know when getHTML() has finished, or worse, has failed. This problem is solved elegantly with async functions. Though you cannot make an async constructor () { ... }, you can initiate a promise in the constructor, and await it in the functions that depend on it.

Example gist of an async property in a class constructor.

Christophe Marois
  • 6,471
  • 1
  • 30
  • 32