Dan D Kim

Let's share stories

Anti-Patterns in Javascript Promises

2019-07-01 Dan D. Kimjavascript

Promises are great. They are sexy. They really changed the game for those spaghetti callbacks in Javascript.

But still, you could mess it up into a clusterfuck if you bring in bad practices (duh).

So, let’s get a brief intro to some shit NOT to do.

.then(success, fail) anti-pattern

I briefly made this specific point in my Javascript Promises Ramp-up, but this is so important.

Never use the .then(success, fail) pattern. Just don’t. There’s no reason to use this.

Let me explain.

Workflow Diagram

The following diagram shows how errors are handled with a .then(success, fail) pattern.

"Workflow of .then(success, fail)"

The problem? Well, if an error is thrown from inside the success, it won’t be caught.

You can avoid this by using a .then().catch() pattern.

Look at the following diagram to see what I mean.

"Workflow of .then().catch()"

Code makes more sense too

If you have coded before, you must know about the try - catch pattern.

try {
  someSketchyShit()
} catch (err) {
  handleError(err)
}

That pattern above is like, almost language-agnostic. Do I even need to explain how well-spread this is?

Anyways, the point is to keep it simple. So instead of doing it like:

doStuff()
  .then(result => {
    ...
  }, err => {
    handleError(err)
  })

Just use the then - catch pattern.

doStuff()
  .then(result => {
    ...
  })
  .catch(err => {
    handleError(err)
  })

Wrap synchronous and asynchronous functions with Bluebird

What do you do when you have to cover async and sync functions with try - catch?

doWorkSync() // function that might throw error
doWorkAsync() // function that might throw error

Do you wrap them all up in one big try - catch?

No, because it won’t catch the asynchronous operations.

try {
  doWorkSync()
  doWorkAsync()
} catch (err) {
  // Will not catch error from async function
}

Do you wrap the sync in try - catch and nest the async chain inside?

No, the async error won’t be caught in the synchronous catch block.

try {
  doWorkSync()
  doWorkAsync()
    .then(() => { ... })
    .catch(err => throw err)
} catch (err) {
  // Will STILL not catch error from async function
}

How about if you handle the error instead of throwing it?

Well, that works, but…

try {
  doWorkSync()
  doWorkAsync()
    .then(() => { ... })
    .catch(err => handleError(err))
} catch (err) {
  ...
}

What’s the point of having the async chain inside the synchronous try - catch? Let’s assume the logic of doWorkSync and doWorkAsync are independent. Could we move doWorkAsync outside of the try - catch?

Yes, but it looks ugly.

try {
  doWorkSync()
} catch (err) {
  ...
}

doWorkAsync()
  .then(() => { ... })
  .catch(err => handleError(err))

Instead, you can use Bluebird’s Promise.try() to chain up synchronous with asynchronous operations.

Promise.try(() => doWorkSync())
  .then(() => doWorkAsync())
  .then(() => ... )
  .catch(err => handleError(err))