Hacker News new | ask | show | jobs
Understanding Node.js Event-Driven Architecture (medium.freecodecamp.com)
72 points by samerbuna 3333 days ago
3 comments

I know I'm in the minority about this, but I still prefer callbacks. In one common case, you're only doing one or two async things in a given area of the code, and in that case the async primitive you choose doesn't really matter. In another common case, you're doing a lot of async things that depend on each other's outcomes. In that case, I find that it's easier to build a reusable pattern for handling the specific type of async interaction you have using callback-based functions, whereas with promises and async/await, you tend to try to write straight line code that can't be reused and hides the asynchrony in a way that makes it difficult to debug.
I'm just too hooked on the async.auto() pattern to give up callbacks. The ability to break a large flow down into functions and then use a dependency based structure to automatically execute the flow with the most optimal concurrency is incredible. And the resulting code is very clean, with each functional step in the flow living on the same indent level. Refactoring and adding new steps is very easy as you can just add a new dependency to one of the existing functions or add a new function that depends on one of the other functions.

https://caolan.github.io/async/docs.html#auto

That looks interesting - as long as you stay in JS land. Once you move into typescript this kind of system will be impossible to typecheck. Promises and their compositions will typecheck just naturally compared to that.
Yep, there is the autoInject which injects previous dependent functions return values as parameters instead of as properties of a context object, so that may help some, but it still breaks typechecks.

I wish that promises had a concept similar to this. It's simply too annoying for me to manually refactor a bunch of Promise.all() calls to manually adjust the concurrency of the logic flow compared to using the dependency pattern of async.auto().

And based on what I've seen this is a common problem in both async functions, generators, and promises. Since it is difficult to properly execute a tree of functional steps with the maximal concurrency I see code all the time which just runs async functions or promises in a series instead of taking advantage of the ability to concurrently execute two branches of the tree at the same time.

Have you looked at http://bluebirdjs.com/docs/api/promise.map.html - which allows you to control the concurrency of Promises easily?
Here you can read about the advantages of using Promises, it s very interesting, though a bit long. https://blog.jcoglan.com/2013/03/30/callbacks-are-imperative...

Promises are easier to compose because you can pass them around as values. Meanwhile callbacks are used only for their side-effects, they do not return anything.

Good not just me. I guess at this point I'm kind of ambivalent, but sometimes still think async.js is a lot easier to reason about.

Have not yet moved to async/await though.

How can you understand the node event driven architecture without mentioning the node event loop? You can't.

The event loop is not something you can be abstracted from, it is a core component of node that you need to be aware of all the time.

If you have long lived code within an event loop tick you will starve the I/O and everything will start timing out.

Then, freecodecamp (and every other coding camp) do an incredible disservice to society by telling everyone that ever touched a computer for 5 minutes that they're "full-stack engineers", a denomination that is in itself very questionable too.

It's like calling myself a veterinarian because I walked my dog, or calling myself a chef because I fried an egg. These camps promote a very oversimplified vision of what it means to be a software engineer.

That's why Python does the contrary and let you use the event loop explicitly. But then the problem is that you have to deal with it even for simple things.

I think the balance is missing here.

I think the balance is node, which allows an implicit event loop (via async/await), or an explicit event loop (via using coroutines as standins for async functions and manually yielding them).
There is "the" event loop in node. http://docs.libuv.org/en/v1.x/loop.html
Last time I checked (~ a year ago admittedly), the node.js api itself (e.g. fs) was still mostly callback-based and had little built-in support for promises. You either had to resort to 3rd-party wrapper modules or wrap the calls yourself, resulting in a lot of clutter in your code.

Does anyone know if the situation has changed by now and you can obtain promises from core functions directly?

The situation is still that callbacks are king in the node core libraries. But there is some talk about how to move forward[0], however I don't think there has been much headway as of the last time I really looked into it about 6-months ago.

That being said, there are some fantastic libraries which you can use as a drop-in replacement for the core libraries provide a promise based API, but nothing in core directly.

[0] https://github.com/nodejs/NG/issues/25

Can you recommend please some of these non CB based libraries?
The only one i've used before is Bluebird's `promisifyAll` method.

So when you require libraries, you can do so like:

    const fs = Promise.promisifyAll(require('fs'))
    // ... later    
    const fileContents = await fs.readFileAsync('filename.txt')
Basically it just adds "Async" after all functions that use node style callbacks.
mz (https://www.npmjs.com/package/mz) is my go-to. You just prefix the core lib ("fs") with "mz/" ("mz/fs") and you get the promised version.

    const fs = require("fs");
    fs.exists(`${ __dirname }/1`, (exists1) => {
      fs.exists(`${ __dirname }/2`, (exists2) => {
        console.log(JSON.stringify([exists1, exists2]));
      });
    });
vs

    const fs = require("mz/fs");
    Promise.all([
      fs.exists(`${ __dirname }/1`),
      fs.exists(`${ __dirname }/2`),
    ])
    .then((existers) => {
      console.log(JSON.stringify(existers));
    });
Or

    const fs = require("mz/fs");
    (async () => {
      console.log(JSON.stringify(await Promise.all([
        fs.exists(`${ __dirname }/1`),
        fs.exists(`${ __dirname }/2`),
      ])));
    })();
supertest-as-promised is pretty great for testing purposes.
Regular supertest supports promises.