Anti-Patterns in Javascript Promises
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.
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.
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))