Skip to content

Instantly share code, notes, and snippets.

@RinatMullayanov
Last active August 29, 2015 14:21
Show Gist options
  • Save RinatMullayanov/a4db2500e71a9886bf04 to your computer and use it in GitHub Desktop.
Save RinatMullayanov/a4db2500e71a9886bf04 to your computer and use it in GitHub Desktop.

I said before that the magic of promises is that they give us back our precious return and throw. But what does this actually look like in practice?

Every promises gives you a then() method (or catch(), which is just sugar for then(null, ...)). Here we are inside of a then() function:

somePromise().then(function () {
  // I'm inside a then() function!
});

What can we do here? There are three things:

  • return another promise
  • return a synchronous value (or undefined)
  • throw a synchronous error

That's it. Once you understand this trick, you understand promises. So let's go through each point one at a time.

1. Return another promise

This is a common pattern you see in the promise literature, as in the "composing promises" example above:

getUserByName('nolan').then(function (user) {
  return getUserAccountById(user.id);
}).then(function (userAccount) {
  // I got a user account!
});

Notice that I'm returning the second promise – that return is crucial. If I didn't say return, then the getUserAccountById() would actually be a side effect, and the next function would receive undefined instead of the userAccount.

2. Return a synchronous value (or undefined)

Returning undefined is often a mistake, but returning a synchronous value is actually an awesome way to convert synchronous code into promisey code. For instance, let's say we have an in-memory cache of users. We can do:

getUserByName('nolan').then(function (user) {
  if (inMemoryCache[user.id]) {
    return inMemoryCache[user.id];    // returning a synchronous value!
  }
  return getUserAccountById(user.id); // returning a promise!
}).then(function (userAccount) {
  // I got a user account!
});

Isn't that awesome? The second function doesn't care whether the userAccount was fetched synchronously or asynchronously, and the first function is free to return either a synchronous or asynchronous value.

Unfortunately, there's the inconvenient fact that non-returning functions in JavaScript technically return undefined, which means it's easy to accidentally introduce side effects when you meant to return something.

For this reason, I make it a personal habit to always return or throw from inside a then() function. I'd recommend you do the same.

3. Throw a synchronous error

Speaking of throw, this is where promises can get even more awesome. Let's say we want to throw a synchronous error in case the user is logged out. It's quite easy:

getUserByName('nolan').then(function (user) {
  if (user.isLoggedOut()) {
    throw new Error('user logged out!'); // throwing a synchronous error!
  }
  if (inMemoryCache[user.id]) {
    return inMemoryCache[user.id];       // returning a synchronous value!
  }
  return getUserAccountById(user.id);    // returning a promise!
}).then(function (userAccount) {
  // I got a user account!
}).catch(function (err) {
  // Boo, I got an error!
});

Our catch() will receive a synchronous error if the user is logged out, and it will receive an asynchronous error if any of the promises are rejected. Again, the function doesn't care whether the error it gets is synchronous or asynchronous.

This is especially useful because it can help identify coding errors during development. For instance, if at any point inside of a then() function, we do a JSON.parse(), it might throw a synchronous error if the JSON is invalid. With callbacks, that error would get swallowed, but with promises, we can simply handle it inside our catch() function.

//#1: the promisey pyramid of doom
//wrong
remotedb.allDocs({
include_docs: true,
attachments: true
}).then(function (result) {
var docs = result.rows;
docs.forEach(function(element) {
localdb.put(element.doc).then(function(response) {
alert("Pulled doc with id " + element.doc._id + " and added to local db.");
}).catch(function (err) {
if (err.status == 409) {
localdb.get(element.doc._id).then(function (resp) {
localdb.remove(resp._id, resp._rev).then(function (resp) {
// et cetera...
//right
remotedb.allDocs(...).then(function (resultOfAllDocs) {
return localdb.put(...);
}).then(function (resultOfPut) {
return localdb.get(...);
}).then(function (resultOfGet) {
return localdb.put(...);
}).catch(function (err) {
console.log(err);
});
//#2: WTF, how do I use forEach() with promises?
// I want to remove() all docs
//wrong
db.allDocs({include_docs: true}).then(function (result) {
result.rows.forEach(function (row) {
db.remove(row.doc);
});
}).then(function () {
// I naively believe all docs have been removed() now!
});
//right
db.allDocs({include_docs: true}).then(function (result) {
return Promise.all(result.rows.map(function (row) {
return db.remove(row.doc);
});
}).then(function (arrayOfResults) {
// All docs have really been removed() now!
});
//#3: forgetting to add .catch()
//To avoid this nasty scenario, I've gotten into the habit of simply adding the following code to my promise chains:
somePromise().then(function () {
return anotherPromise();
}).then(function () {
return yetAnotherPromise();
}).catch(console.log.bind(console)); // <-- this is badass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment