Use ES2017’s async/await in your Ember Project Today!

After recently spending some time with C#’s async/await, I found myself wishing for the same features in JavaScript. I knew such a language feature was in the works, but it had been quite a while since I’d mentally filed it under, “Won’t it be nice when,” so I thought I’d check to see if async/await is ready for use in an Ember app.

Async/await?

Modern JavaScript in general, and Ember specifically, manage asynchronous control flow through the use of promises. The combination of promises’ chainable .then() with ES6 Arrow Functions makes for a reasonably legible sequence of events, like in this Ember data loading example:


export default Ember.Route.extend({
  model(){
    return Ember.$.getJSON('/api/location')
      .then((loc) => Ember.$.getJSON(`/api/weather?lat=${loc.lat}&long=${loc.long}`))
      .then((weather) => Ember.$.getJSON(`/api/advice?weather=${weather.weather}`));
  },
});

(We don’t need to .then() the final getJSON() call, as Ember’s model hook handles promises.)

This is a big improvement over building something like this without promises, but it’s still a bit unwieldy. With async/await, the above code flattens out nicely:


export default Ember.Route.extend({
  async model(){
    const loc = await Ember.$.getJSON('/api/location');
    const weather = await Ember.$.getJSON(`/api/weather?lat=${loc.lat}&long=${loc.long}`);
    return Ember.$.getJSON(`/api/advice?weather=${weather.weather}`);
  },
});

For more on async/await, check out Matt’s February post JavaScript Promises – How They’ll Work Someday.

Using async/await in an Ember-CLI Project

Is it possible?

Yes! By default, Ember-CLI uses the Babel JavaScript compiler, which supports async/await out of the box! I usually think of JavaScript compilers as a way to provide today’s JS features to yesterday’s browsers, but this is a good reminder that they can also bring tomorrow’s JS features to today’s browsers!

Is it a good idea?

This is a little trickier to answer. Using an experimental language feature is a bit like adopting third-party code: You have to gaze into your crystal ball and predict whether it’s going to be well-supported down the road, weighing the benefits against the risk.

Fortunately for us in this case, ECMAScript language governance occurs publicly, and we can see that async/await has advanced to the final stage of the the proposal process. It’s already in 2017 draft spec.

Enabling async/await in your Ember-CLI build

This is the easy part! You need only enable Babel’s polyfill in ember-cli-build.js:


  var app = new EmberApp(defaults, {
    // Add options here
    babel: {
      includePolyfill: true
    },
  });

We’re nearly there, but there’s one last hurdle. The first time you actually use async or await, you’ll likely get build warnings and test failures from JSHint.

Switching Static Analysis Tools

Over the course of my last few projects, I’ve become an advocate of static analysis tools like Rubocop. The Ember community agrees, too, as new Ember-CLI projects are generated to use one.

At the moment, that’s JSHint, which unfortunately does not yet support async/await. It should eventually, but for now the easiest solution is to jump ship and switch to ESLint. (It looks like Ember’s moving this way, too.)

To do this, simply ember install ember-cli-eslint. This will remove the existing ember-cli-jshint package, optionally remove your .jshintrc configuration files, and emit new .eslintrc.js configs with sane defaults.

Finally, edit your new config files to specify ecmaVersion: 8, and to port over any customizations you had previously made to your jshint configs (e.g. adding globally-defined test helpers).

Conclusion

So it turns out that, while I wasn’t looking, “Won’t it be nice when” has become, “This is nice now.” Are you looking forward to the arrival of any new features in your daily languages?
This came about because I happen to be using both C# and JavaScript on this project. What language feature differences have become conspicuous as you traverse your full stack?

Here is a GitHub diff of the changes discussed in this post applied to an example Ember-CLI 2.10 project.

Conversation
  • Casey Watts says:

    `includePolyfill: true` ends up adding 30kb (gzipped, minified) to the production build. For some apps this is okay.

    But if you want to keep your application payload slimmer, there are two more ways you can get this working:
    https://gist.github.com/caseywatts/7c01654f74fbe402dfaadfa144965adb

  • Casey Watts says:

    In test code, async/await works perfectly for me :)

    However, with application code there are still some issues with `Ember.run`. RSVP Promises include an Ember.run implicitly, but await code would need Ember.run to be explicit wherever it’s relevant. This RFC explains the situation:
    https://github.com/emberjs/rfcs/issues/175

    For now, my team and I aren’t going to use async/await in our application code.

  • Ernesto says:

    I used the https://github.com/machty/ember-maybe-import-regenerator way, suggested by Casey Watts in one of those links above. It worked ok.

    However, I am disappointed to find out that you can’t declare an observer function as async. It won’t work. At least it did not work for me. It works when I declare class methods async though.

  • Comments are closed.