Bluebird’s Bad-Practice Docs

The Terrible Best-Practices Used by Node.js Devs

--

This article’s controversial. I know that. I’m publishing it hoping to change the industry in a positive direction, but I know there’s always the possibility of grinding gears or being completely wrong.

There are plenty of promise libraries out there, but by far the most popular, and probably the one you’ll actually see used in production, is Bluebird. It’s a fantastic promise library with lots of features, but has a big gotcha.

What’s in a Name?

Instead of being imported as Bluebird, the official docs and common use is to name it Promise like so:

const Promise = require('bluebird')// orimport Promise from 'bluebird'

This is because Bluebird actually came about long before promises were native to JavaScript. While importing it as Promise allows you to write the same promise code you always do along with some extras, this can be pretty confusing to other developers because not all promises are Bluebird promises.

For instance, if you import another library that uses promises, they won’t be Bluebird promises. Also, if you use fetch those also won’t be Bluebird promises. Lastly, if you create your own promises but forget to import Bluebird somewhere else in your app, those also won’t be Bluebird promises.

OVERWRITE ALL THE THINGS!

The solution to this is to overwrite the global Promise with Bluebird. I’m against this for many reasons, but one of them is that suddenly your entire application’s version of JavaScript is no longer following the norm. It’s another one of those things that’ll nab new devs on your project which you’ll have to explain.

There are plenty of other gotchas that occur from overriding JavaScript globals. Off the top of my head it’d be very difficult to context switch between projects that do and don’t use Bluebird. Another would be the ability to switch libraries. As soon as you tie yourself to Bluebird, you’re stuck with it. What if it has a big security flaw? What if you want to use RxJS instead? Instead of being able to write around it, you’ve forced yourself into using it for everything.

Love Your Neighbor (It’s You in 6 Months)

I might sound spastic since plenty of people are using Bluebird like this today, but it’s not something I’d recommend from my personal experience. As soon as the industry changes and your app now uses legacy technology, you wanna be ready for change.

This is all about maintenance. At some point, doing things the old way (or really, the wrong way) will make it more-costly to hire new developers. And when you ask devs to work on the legacy code, based on my experience, they’re going to be hoping it’s temporary or that a rewrite is coming soon.

While you probably won’t want to use both the native implementation and Bluebird at the same time, my recommendation is to always import Bluebird where you want to use it and always name the imported library Bluebird to signal all developers “I’m using the Bluebird promise library”.

With a change like that, other devs won’t have to guess or constantly check if Bluebird was imported into each file every time they’re in one. That way, you always know what kind of promise you had. Now your app can be easily maintained and refactored over time without worrying about this looming Bluebird problem.

Promisify

Like bindCallback in RxJS, you can use Promise.promisify to automagically wrap callback functions in a promise. I used this method in the first version of my lights controller software at home since it got tiring converting callbacks to promises each time I wanted to use one.

On the other hand, I don’t recommend ever using Promise.promisifyAll. I get it has its uses, but it mutates the object you give it and well, I like to be explicit about these things especially because now you’re tying your regular libraries to Bluebird. I don’t like changing the landscape of Node.js libraries or your browser’s window object since developers down the line won’t know what the heck’s going on.

I CAN HAS ALL THE PROBLEMS?

For instance, a developer might import fs expecting a native Node.js library only to find every function has a custom promisified async version that returns a promise instead of taking a callback, and nothing in the codebase matches the official Node.js docs. It’s a great way to confuse people.

And postfixing the promisified versions “async” makes it even more confusing because technically fs.readFile is already async, but now there’s fs.readFileAsync which is also async, but specifically promisified. Why not fs.readFilePromisified? Too long? Maybe you shouldn’t be mutating fs.

Take it another way, now there are two completely different ways of calling functions on native libraries. Instead of wrapping those calls in your own utility file (also comes with its downsides), you’re relying on code reviews, possibly linting rules, and word-of-mouth to ensure devs are using the preferred methods.

That, or you’ll just have a large codebase containing a mishmash of various coding styles. Always leaves a headache for the next dev who takes over and wonders why it’s done one way here and a completely different way in another place.

If you ever worked with JavaScript in the past 20 years, you’d come across the same kinda stuff causing your nightmares (you could even overwrite undefined), and it was shamed outta use around the time React became popular. Bluebird predates this change, but latest docs also don’t warn against it; in fact, they encourage it.

Mutating libraries and global values like this is living legacy ever after. It’s a great way to tie your project into a knot around a single library.

Conclusion

I know this is controversial. I expect devs using Bluebird to have their own horror stories, but the reality is, I’ll probably get a lot of negative feedback. If you found flaws in my evaluation, I encourage you to provide your feedback so I know what to improve the next time I write an article.

More Reads

If you liked what you read, please checkout my other articles on similar eye-opening topics:

--

--