Before we begin

This post uses es6 syntax

// This
var add = function (x) {
  return x + 1;
};

// Is the same as this in es6
let add = (x) => x + 1;

The Basics

Promises are pretty cool things.

They allow you to take code like this.

fetchPeople((people) => {
  getNames(people, (names) => {
    upcase(names, (upcasedNames) => {
      display(upcasedNames);
    });
  });
});

And turn it into this

fetchPeople
  .then(getNames)
  .then(upcase)
  .then(display);

Pretty nice, right?

Lets take a look at how we can create a promise.

let getNames = (people) {
  return new Promise((resolve) => {
    let names = people.map((person) => person.name);
    resolve(names);
  });
};

Now we can use our promise.

getNames([{
  "name": "Steve",
  "age": 22
}, {
  "name": "Bob",
  "age": 33
}]).then((names) => {
  console.log(names); // [ 'Steve', 'Bob' ]
});

or when we make it break.

getNames(undefined, (names) => {
  // this code is never reached
}).catch((e) => {
  console.log(e); // TypeError: Cannot read property 'map' of undefined
});

There is a lot going on there so lets break it down.

Resolve

Calling resolve tells the promise that it is executed successfully

let fetchPeople = (people) => {
  return new Promise((resolve) => {
    db.find('people', (people) => {
      // people have been fetched so we can resolve
      resolve(people);
    });
  });
};

It’s like calling a callback.

let fetchPeople = (done) => {
  db.find('people', (people) => {
    // people have been fetched so we can call the callback
    done(people);
  });
};

Then

Then gets called when the promise is finished executing successfully.

fetchPeople().then((people) => {
  // I now have people
});

Catch

Catch is what makes promises especially powerful it allows you to use exceptions in asynchronous code.

Traditional callbacks suffer from clunky exception handling.

If an exception happens in the following code.

let getNames = (people, done) => {
  done(people.map((person) => person.name));
};

We can’t catch it

try {
  getNames(undefined, (names) => {
    // this is never reached
  });
} catch (e) {
  // this is never reached either
}
// even though a TypeError: Cannot read property 'map' of undefined exception gets thrown

So we have to move the try catch code into the getNames function.

let getNames = (people, done) => {
  try {
    done(null, people.map((person) => person.name));
  } catch (e) {
    done(e, null);
  }
};

So now we can.

getNames(undefined, (err, names) => {
  if (err) {
    // deal with the error
  } else {
    // do something with names
  }
});

This is not exactly ideal as we are essentially duplicating the try catch logic.

What’s even worse is that if we want to do exception handling within the callback we have to add an additional try catch.

getNames([{
  "name": "Steve",
  "age": 22
}, {
  "name": "Bob",
  "age": 33
}], (err, names) => {
  if (err) {
    // deal with the error
  } else {
    try {
      names.someUndefinedFunction();
    } catch (e) {
      console.log(e); // TypeError: [ "Steve", "Bob" ].someUndefinedFunction is not a function
    }
  }
});

Compare this to a promise based version of getNames.

let getNames = (people) => {
  return new Promise((resolve) => {
    resolve(people.map((person) => person.name));
  });
};

getNames(undefined).then((names) => {
  // I never get called
}).then((e) => {
  // But I do
  console.log(e); // TypeError: Cannot read property 'map' of undefined
});

getNames([{
  "name": "Steve",
  "age": 22
}, {
  "name": "Bob",
  "age": 33
}]).then((names) => {
  names.someUndefinedFunction();
}).catch((e) => {
  console.log(e); // TypeError: [ "Steve", "Bob" ].someUndefinedFunction is not a function
});

That’s it for the basics keep an eye out for a follow up post on promise patterns.