Knowing Enough to Be Dangerous

I have been working on a couple of Ember.js projects lately. While Ember is a pretty fun framework to learn, the learning curve is steep. Part of this curve not only stems from the fact that Ember.js is a large framework with its own opinions, but it also leverages the power of “future JavaScript.” This only adds to the complexity and heightens the barrier of entry for new Ember users.

I jumped into Ember without really having a good understanding of promises. I knew just enough to be dangerous, but when Ember would silently fail, I would spend time debugging and in turn trolling myself.

Promises allow for you to handle asynchronous code in JavaScript. It’s a relatively new approach compared to the nested callbacks approach. Promises are available outside of Ember, but implementation and browser support will be different.

A promise is like an object with two functions. These functions are then() and catch() (success and unsuccessful).

  let promise = {
    then: function(){}, // when successful
    catch: function(){} // when not successful
  }

How Do You Work With a Promise

Here is an example of an Ember.js $ajax call that returns a promise.

  let promise = this.store.findAll('user');
  return promise.then((users) => {
    console.log(users);
    return users;
  });

.then() could have been written promise.then(function() {}); as well, but I prefer the fat arrow syntax and its binding of this.

In the above code .then() returns users if the request is successful. What happens if it is not successful?

 promise.catch(function(err) {
   console.log("ERROR", err);
 });

Just use .catch().

Or you can use the shorthand and chain the .catch().

  promise.then((users) => {
      console.log(users);
      return users;
    }).catch((error) => {
      console.log("ERROR", err);
    });

What is really interesting about a promise is the ability to chain another promise onto the first. This becomes increasingly helpful in code readability. If you are familiar with js callbacks then you might also have heard the term callback hell. Essentially, each JavaScript callback is nested inside of another, along with its success and error handling functions. At first glance, it is nearly impossible to decipher what the outcome should be, not to mention the pain of tracking down bugs. With promises, you can chain them off one another and also have your error handling in one place! But if you’re not careful, you can end up nesting callbacks, and miss the power of promises. I won’t show a JavaScript callback example, but I can create the same idea using our promise from above.

NOT GREAT

  promise.then((users) => {
    console.log(users);
    this.store.findAll('posts').then((posts) => {
      console.log(posts);
      this.store.findAll('comments').then((comments) => {
        console.log('comments');
      });
    });
  });

As you can see from the above example, we are getting a structure that resembles a pyramid, nesting further and further in. Using async callbacks this way while using a promise is not ideal.

SO MUCH BETTER

  promise.then((users) => {
    console.log(users);
    return this.store.findAll('posts');
  }).then((posts) => {
    console.log(posts);
    return this.store.findAll('comments');
  }).then((comments) {
  console.log(comments);
  }).catch((error) => {
    console.log("ERROR", error);
  });

Following the above example makes the code easier to reason about, and trace bugs.

Notice the return on return this.store.findAll('posts'), and also on comments. Returning a promise will resolve that promise next, and allows us to chain the .then() function to the promise preceding it. Also notice how there is only one .catch() function at the end of the promise chain. This .catch() will resolve any of the unsuccessful async operations from any of the above promises. “OH YOU FANCY, HUH!”

Wait There is More!

In some circumstances you may want all your async code to resolve together before continuing.

  Promise.all([
    this.store.findAll('users'),
    this.store.findAll('posts'),
  ]).then((data) => {
    let users = data[0];
    let posts = data[1];
    console.log(users, posts);
  });

The .all() syntax allows us to list our promises that we want to return before we continue on. At the end of .all() we have a single .then() function to resolve our data array. The position of the data will match the order in which you listed the promise. So since the call to the store for users is first, it will populate the first position in the returned data array.

Note: The order in which these promises resolve does not depend on the order in which we listed them. Meaning, that the call to fetch posts may return before the call for users. In either event, .then() is not called until both resolve.

Build Your Own

We have covered how to work with promises in this post so far, but I haven’t touched on how to create your own promise. MDN Promise

  let myPromise = new Promise(function(resolve, reject) {
    $.ajax('/countries', {
      success: function(response) {
        resolve(response);
      },
      error: function(reason) {
        reject(reason);
      }
    });
  });

  return myPromise.then((data) => {
    console.log(data);
  }).catch((error) => {
    console.log(error);
  });

Now go make some promises you can keep! Thanks!