JavaScript (and Node) Best Practices
noahbuscher.com
noahbuscher.com
When you design an asynchronous function, you're telling your caller that he can go and do other stuff (e.g. process other requests) while you're fetching his result in the background. Async functions should never block. If making everything async was a good idea, then underscore would be async.
The author also mentions that it is node convention to use callbacks. Sure, that's true. But he's calling his callbacks the wrong way. Node convention is `callback(error, result)`, not `callback(result)`.
Oh and promises ftw.
Articles like this hurt JavaScript's credibility, and certainly doesn't help new JS/node devs.
Using a synchronous programming style (i.e. function returns value) whenever you are performing a synchronous operation is always going to be more clear than using an asynchronous programming style for synchronous operations (i.e. function invokes passed function with value and returns undefined). And that's why many languages have await - to help make the async code have a more synchronous(and more easily readable) programming style.
For example, consider a function with a synchronous implementation. It gets the data from a place that is quick and easy to access, so making it async is not necessary. But later on, that data gets moved to an external database. Now, in the best case, every place where that function is used must be updated to be made asynchronous. In the worst case, every caller of the function, as well as every caller of those functions, and so on, must be updated. And quite often, converting synchronous code to async is no small feat.
I am 100% for making sync functions asynchronous if it's implementation might have to be async in the future.
In fact, I have done so many times for the exact example you have mentioned.
Some I would add:
- Prefer === over ==
- Prefer pure functions whenever possible
- Use promises, not callbacks. Wrap callbacks with promises when necessary. Do this once, and put that in a module.
- *Learn to use Lodash.* I've seen so many wheels reinvented you'd think I worked at Firestone.
- *Learn the rest of the ecosystem.* See above.
- Logic shouldn't go in route handlers. Routes should only orchestrate.
- Learn to love the Array functions. ForEach, Map, Reduce, Filter: use them.
I also have some beef with the article's content.> [...] as a rule on thumb, I always use callbacks for any function, regardless of what it does.
Please don't. Callbacks are a (somewhat - see below) necessary evil, not an idiomatic good. Only use this if what you're doing is actually asynchronous - talking to the network, reading from the disk, making DB calls, etc. Maybe for really computationally expensive stuff (see bcrypt), but in day-to-day web dev you just won't run into that.
> If I know I'm going to be nesting callbacks more than two levels, I'll switch over to a package like async or write my own function quene method to help me out.
No. Do not do that. If you know you're going to be nesting callbacks more than 2 levels deep, just use the async package. Better yet, use promises from the beginning and don't worry about it.
> I know I've already made some people angry, and I'm okay with that.
You shouldn't be. If you've made anyone angry, it's because you're putting bad advice out there. Worse, it's mixed in with some really good advice, making it all the more deceptive.
Once again, I really appreciate the spirit in which this article was written. I just wish it wasn't mixing utter nonsense into otherwise sound advice.
A good Promise library (like https://github.com/petkaantonov/bluebird) even provides mechanisms for writing functions that both return Promises as well as accept callbacks, allowing your API client to choose how they wish to use it.
Also, beware of making a function "asynchronous" by simply having it accept a callback parameter, e.g.:
function myFunc(v, cb) {
someOtherObject.v = v;
cb();
}
myFunc(5, function(err) {
if (err) return console.error(err);
console.log('call done');
});
console.log('call started');
In the above code, you'll see "call done" before you see "call started" because myFunc() isn't really functioning asynchronously.This is yet another reason to use a good Promise library like Bluebird, as it ensures that even if you wrap a synchronous function inside a Promise the resulting execution will flow as if it was asynchronous.
function (err, result) {
//...
}On a related side note, I've started using the Chrome Web Inspector as my only text editor for front end development. It's great because you can map it to your project folder, make edits directly and harness the power of breakpoints, along with the network, console and performance tabs, without constantly switching windows. Breakpoints, in particular, make it much easier to work with callbacks in JS. (I do miss the nice syntax highlighting of Sublime, but Chrome's dev tools provides more powerful features for JS development IMO.)
I also think it encourages new developers to write messy functions and then think "that's ok I've described what it does in a comment above, so the next person will understand it anyway".
Seeing basic examples was really helpful, but a more expansive project that employs these elements would make for good reading.
Huh?