News for hackers. Stuff that matters.
Re:Node v7.5.0 Released by GeneralMaximus on Friday February 03, 2017 @04:45AM (#13558777)
You have a number of ways of keeping a handle on async code now.

1. Native ES6 promises will cover most of your basic needs. See https://developer.mozilla.org/en/docs/Web/JavaScript/Referen.... In fact, unless you have very specific needs not covered by native promises, you shouldn't drop a third party library into your project.

2. For more advanced operations on promises, use Bluebird. See http://bluebirdjs.com and http://bluebirdjs.com/docs/api-reference.html. It has a ton of features built on top of native ES6 promises. If you're using an older version of Node, it also acts as a polyfill. It can also make it easy to work with libraries that only expose a callback-based interface. So yeah, Bluebird is the shit.

3. co is a generator-based control flow library that can make your async code look more like synchronous code. It's pretty cool, but I haven't personally used it in a project, nor do I know of anyone else who uses it. It builds on top of promises and generators, and isn't very hard to understand under the hood. See https://github.com/tj/co. I wouldn't recommend it, but it might be something to play with because ...

4. ... async/await is coming to JavaScript! At a very high level, this pair of keywords is syntax sugar for what co already does as a library. This is the final stage in the evolution of taming callback hell in JavaScript, and it builds on top of everything that has come before (promises and generators). Here's a tutorial: https://blog.risingstack.com/async-await-node-js-7-nightly/

Re:Node v7.5.0 Released by nodesocket on Friday February 03, 2017 @04:58AM (#13558815)
Also highly recommend the npm async library[1] even though the hipster way is now native async/wait or promises. caolan/async has some amazing sugar on nearly every use-case the most common for me being async.auto().

[1] https://github.com/caolan/async

Re:Node v7.5.0 Released by Bahamut on Friday February 03, 2017 @07:03AM (#13559204)
I highly recommend against using that library - it encourages a lot of callback hell we've found at my company, and it is much harder to create nice reusable functions with it vs. promises. bluebird is a wholly superior library for handling async flow.
Re:Node v7.5.0 Released by nailer on Friday February 03, 2017 @08:16AM (#13559560)
> it encourages a lot of callback hell we've found at my company

This is a very odd comment. async provides the same level of indentation as Promises does:

  async.waterfall([
    function(){},
    function(){},
  ])
Here's promises doing the same thing:

  function()
  .then(function(){})
  .then(function(){})
One's a list of functions to be run in order, the other uses method chaining. You may prefer one or the other but saying async 'encourages callback hell' is about as logical as saying promises does.

__Edit__: further to my own comment. The people I know who know their stuff (ie, work on JS itself) and like/prefer Promises do so because they believe that functions should return values.

I don't think anything will be better than either solution until async/await gains wide support. Async/await needs return values, so hence promises, so it's definitely worth knowing promises. But yeah, right now async is fine.

Re:Node v7.5.0 Released by Matthias247 on Friday February 03, 2017 @10:35AM (#13560463)
Promises also work better with Typescript, since it's well defined which type each step returns and which the next function expects. That means the .then(a => next(a)) cascades can be typechecked well. async.waterfall doesn't seem to be typecheckable.
Re:Node v7.5.0 Released by nnq on Friday February 03, 2017 @12:47PM (#13561620)
The huuge difference once you understand its implications is that you can:

    return function().then(() => ...).then(() => ...)
...and the caller of you function can decide to chain even more stuff after, in the simplest case. Most obvious benefits, though not the biggest, is that you simply can return a promise ad drop the ugly extra callback argument that you function needs to have. For example:

    function foo(cb) {
        async.waterfall([
            function(){},
            function(){},
        ])
    }
becomes:

    function foo2() {
        return function()
            .then(function(){})
            .then(function(){});
    }
...and this way the logic ends up where it should be, in the caller, not the callee!

Another simple to explain benefits are the fact that you can .then() a promise 10 times and what it does will only happen once, and then the other times you'll just consume the result. You don't need to care if something is (a) already computed, (b) in the process of being computed, (c) scheduled for computation later etc., you can write the exact same code for all cases. Because you're working with values that have dependencies between this, not with processes that need to happen in a certain order.

The bigger benefits show up when you write more and more code and you see how beautifully it all untangles with promises.

And the fact that once you learn promises you can carry further the knwledge to other async patterns like co and async/await (they are all understandable in terms of promises).

Do yourself and everyone you work with a favor and move away from async please, especially in a team promises make everything so much easier to understand! Same advice of you're writing nodejs libraries: have you functions return promises instead of take callbacks! Drop the intellectual lazyness and grow beyond "async by callbacks", it's the worst coding style ever. And everyone will be able to convert almost instantly from promises to async/await since the logic is similar, just syntactic sugar comes over it.

Re:Node v7.5.0 Released by nailer on Friday February 03, 2017 @01:00PM (#13561736)
As I said: promises return values. That's great. But describing callbacks as "worst coding style ever" and "intellectual lazyness" is hyperbole to describe a style that most of the JS community uses.

Also: not sure if you mean 'you' singular or 'you' plural, but personally I use a combination of callbacks for node stuff with basic async patterns applied (not the 'async' module) and promises.

Re:Node v7.5.0 Released by nnq on Friday February 03, 2017 @02:05PM (#13562293)
The hyperbole is not my style actually, but I use it when talking about JS because I've came to realize the in the JS community it's the only way to be heard.

Generally I see the JS async styles continuum as: (0) callbacks < (1) async module < (2) promises < (3) co < (4) real async/await when language supports it, and I advocate skipping the intermediate steps in this continuum, (1) and (3) because they can lead to either badly composable code or subtle bugs. Also, I stressed the "intellectual lazyness" label because I know lots of people learn async module and this, "ok, this is good enough, I'll stop learning new things about writing better async code in JS".

...and I'm ok with good ol' callbacks where it makes sense for performance reasons, like core part of web frameworks or game engines (even if there is no sensible speed reduction, there is a memory overhead from creating a zillion promises).

Re:Node v7.5.0 Released by mattnewton on Friday February 03, 2017 @10:08AM (#13560238)
It also provides more specific control flow functions that are bug-prone to roll your own with promises. I've learned a lot by looking at the source!
Re:Node v7.5.0 Released by chadscira on Friday February 03, 2017 @11:21AM (#13560853)
Yea but when using promises with async/await its very beautiful.

    await asyncFuncOne();
    await asyncFuncTwo();
No more weird water-falling, and finally proper exception handling. The only gotcha is the fact that we cant awaits at the root of a module, and we need to wrap it in a async function.
Re:Node v7.5.0 Released by wildpeaks on Tuesday February 07, 2017 @11:24PM (#13595736)
The 250Kb extra weight to the Babel bundles (compared to the equivalent callback version) is quite frustrating though.
Re:Node v7.5.0 Released by increment_i on Friday February 03, 2017 @08:45AM (#13559707)
I use Node a lot, and I find myself reaching for the async library any time the project reaches a non-trivial size and there's a lot of communication going on between services. I don't find it all that hard to read (the async.waterfall method looks somewhat like promises chaining) I suppose I should spend a weekend or two steeping myself in promises and es7. It's mildly annoying to have to learn a new async paradigm every couple years, and like others have pointed out, the Node core hasn't changed - its still callbacks.
Re:Node v7.5.0 Released by zachrose on Friday February 03, 2017 @07:54AM (#13559433)
So much of the promise-based code I see looks almost the same as it would with plain callbacks, just with the callbacks plopped into .then(). Am I missing something? How do promises make for more reusable functions?
Re:Node v7.5.0 Released by vkjv on Friday February 03, 2017 @08:08AM (#13559515)
In my opinion, these are the major ones:

1. Promises are a value. They can be passed to other functions. This simplifies a lot of control flow.

2. The `.then` method on a promise is similar to `flatMap` in other languages / libs. The next chain will wait for any nested promises to complete. This flattens callbacks so you can see the flow instead of the pyramid of doom.

3. Native promises include a bit of sugar, such as Promise.all. You need a library to do this with callbacks.

4. Promises are the backbone of async/await. You need them to use it.

Example:

    await const [a, b] = Promise.all([getA(), getB()]);
Re:Node v7.5.0 Released by jcheng on Friday February 03, 2017 @10:58AM (#13560669)
5. Error handling and even successful termination is very, very easy to get wrong with nested callbacks (forgetting to call a callback, or forgetting to catch unexpected exceptions and pass them to the callback, means the function never returns at best or brings down the whole process at worst). Promises make this much easier by automatically propagating errors for you down the promise chain (though you or your caller do still need to handle the error at the end of the chain).
Re:Node v7.5.0 Released by rictic on Friday February 03, 2017 @12:14PM (#13561345)
Nit, but await is an operator, not an annotation on a statement. So that should be written as:

    const [a, b] = await Promise.all([getA(), getB()]);
Re:Node v7.5.0 Released by disease on Friday February 03, 2017 @09:34AM (#13560016)
I'm surprised no one has mentioned my favorite aspect of Promise, which is the fact that you can plop a .catch at the end of a chain and it will do the error handling there regardless of where in the chain it failed. Doing this with long chains of asynchronous calls before Promise was a huge pain in the ass.
Re:Node v7.5.0 Released by thealfreds on Friday February 03, 2017 @08:13AM (#13559550)
Chaining and returning can keep things nice and neat. Say you are following a Controller -> Service -> Repo implementation.

Controller calls the service.

Service returns a call to a repo with any mapping that needs to occur.

Repo calls out to database or external api and returns the promise.

Makes the work very boring and repetitive for the most part which is a good thing I think.

    function someController(req, res) {
        UserService.getUser(req.session.user)
            .then(SomeService.getSomething)
            .then((val) => res.json(val))
            .catch(handleError);
    }

    function getSomething(user) {
        return SomeRepo.getSomething(user)
            .then(MapSomething)
            .catch(alternativeHigherLevelCatch);
    }

    function getSomething(user) {
        return new Promise((resolve, reject) => {
            database.getSomething(
                'defined parameters',
                user,
                resolve,
                reject
            )
        });
    }
Re:Node v7.5.0 Released by rhinoceraptor on Friday February 03, 2017 @11:24AM (#13560885)
It's trivial to refactor that code to use only callbacks, without ending up with callback hell.

You certainly want to call UserService.getUser for many different controllers. Why manually do that for each controller? Simply make a middleware to get the user.

If you take this to its logical conclusion, you end up with small, composable middleware functions, and controllers that are pure functions.

Re:Node v7.5.0 Released by thealfreds on Friday February 03, 2017 @11:53AM (#13561147)
Well sure with a single service and a single mapper etc. Once you start tacking on multiple it keeps following the pattern above with promises and just extends with `.then`'s.

Also agree middleware is a good solution which can go through and provide a user object directly onto the request or another level of abstraction created if you prefer. That was simply to show how multiple service calls could be easily tied together rather than a full and usable example.

A benefit of callbacks though, at least from my experience, is error messages when testing with mocha / chai. I only have experience with jasmine, mocha, and chai but the errors when test driving were much easier to follow with callbacks.

Re:Node v7.5.0 Released by latch on Friday February 03, 2017 @08:07AM (#13559509)
Because promises are chainable, it can take N level-deep code down to 1 level deep. So that's your first big win.

The second one is that you can await a promise, which turns your 1-level-deep code to 0 levels deep.

Re:Node v7.5.0 Released by taternuts on Friday February 03, 2017 @01:48PM (#13562132)
I wouldn't necessarily say they make for more reusable functions. I think primarily their purpose is to provide a cleaner abstraction for dealing with async code flow. Plus I just love seeing code like:

doSomething.then(stepTwo).then(stepThree).catch(errHandler);

Re:Node v7.5.0 Released by zachrose on Friday February 03, 2017 @10:17AM (#13560299)
I should have added that I'm asking to compare the special parts (e.g. reduce, waterfall, etc) of Bluebird vs the async library.
Re:Node v7.5.0 Released by egeozcan on Friday February 03, 2017 @08:02AM (#13559480)
You can take the part before `.then()` and pass it around.
Re:Node v7.5.0 Released by woudsma on Friday February 03, 2017 @08:09AM (#13559517)
yes that works quite great. you have to define a 'async' function, in which you can use the 'await' keyword.

async getDoc(doc) {

  const result = await db.get(doc)

  console.log('result:', result)
}

// instead of:

function getDoc(doc) {

  db.get(doc, result => {
    console.log('result:', result)
  });
}

// or

function getDoc(doc) {

  db.get(doc)
    .then(result => 'result: ' + result)
    .then(::console.log)
    .catch(::console.error)

}
Re:Node v7.5.0 Released by mmmpop on Friday February 03, 2017 @03:28PM (#13563016)
The "hipster" way? Do they wear a silly hat and have little-to-no pant break?
Re:Node v7.5.0 Released by sotojuan on Friday February 03, 2017 @08:31AM (#13559646)
IMO promises and Bluebird made `async` obsolete.
Re:Node v7.5.0 Released by mayank on Friday February 03, 2017 @01:10PM (#13561822)
They certainly did not, if for nothing other than `async.auto()`, which automatically runs a dependency graph of async functions.
Re:Node v7.5.0 Released by phs2501 on Friday February 03, 2017 @03:56PM (#13563232)
A Promise/async/await implementation of 'async.auto' is trivial, as you can use the behavior of promises themselves to avoid solving the graph:

  async function Promise_auto(tasks) {
      const keys = Object.keys(tasks);
      const results = { };
      const taskPromises = { };
      function runTask(key) {
          if (!taskPromises[key]) {
              taskPromises[key] = (async () => {
                  let fn = tasks[key];
                  if (fn instanceof Array) {
                      const deps = fn.slice(0, -1);
                      fn = fn.slice(-1)[0];
                      await Promise.all(deps.map(runTask));
                  }
                  results[key] = await fn(results);
              })();
          }
          return taskPromises[key];
      }
      await Promise.all(keys.map(runTask));
      return results;
  }
Usage example (terms intentionally out of order):

  (async () => {
      const start = new Date();
      const results = await Promise_auto({
          write_file: ['get_data', 'make_folder', async (results) => {
              console.log('in write_file', results);
              await Promise.delay(1000);
              return 'filename';
          }],
          email_link: ['write_file', async (results) => {
              console.log('in email_link', results);
              await Promise.delay(1000);
              return {file: results.write_file, email: '[email protected]'};
          }],
          get_data: async () => {
              console.log('in get_data');
              await Promise.delay(1000);
              return [ 'data', 'converted to array' ];
          },
          make_folder: async () => {
              console.log('in make_folder');
              await Promise.delay(900);
              return 'folder';
          },
      });
      console.log('results = ', results);
      console.log(`It took ${(new Date() - start) / 1000} seconds`);
  })();
Re:Node v7.5.0 Released by spion on Friday February 03, 2017 @09:28PM (#13565286)
Here is a complex dependency graphs built with no nesting and no "special" methods, only a combination of `then` and `all`

  function test(name1, name2) {
    let user1 = User.find(name1)
    let user2 = User.find(name2)
    let post1 = user1.then(u => Posts.getLast(u.id))
    let post2 = user2.then(u => Posts.getLast(u.id))
    let comparison = Promise.all([post1, post2])
      .then(([p1, p2]) => DiffService.compare(p1, p2))
    let email1 = Promise.all([user1, comparison])
      .then(([u1, c]) => Email.send(comparison.text(), u1.email))
    let email2 = Promise.all([user2, comparison])
      .then(([u2, c]) => Email.send(comparison.text() ,u2.email))
    return Promise.all([email1, email2])
  }
Re:Node v7.5.0 Released by ykler on Friday February 03, 2017 @04:54AM (#13558800)
I am confused. Your recommendations 1 and 2 seem to conflict with one another. I use Bluebird now for promisification, and I also used a few other features including the resource management (disposers). Does this mean that I will be using Bluebird and not native promises for the foreseeable future? Or can/should I use Bluebird with native promises? (Or would that just make things slower if it is even possible?)
Re:Node v7.5.0 Released by GeneralMaximus on Friday February 03, 2017 @05:14AM (#13558874)
Automatic promisification and the disposer pattern are both Bluebird-specific features that native ES6 promises don't support, so you'll have to continue using Bluebird if you rely on them.

However, promises returned by Promises/A+ compliant libraries -- and this includes native ES6 promises -- are interoperable with each other. That means you can mix Bluebird promises with native promises for the most part. E.g, passing an ES6 promise to Bluebird's Promise.all will work just fine. This works up to a point, and breaks when you try to use a Bluebird-specific function that's expecting additional functionality to be attached to the promise object.

I don't see why mixing Bluebird promises with ES6 promises would make anything slower. You shouldn't worry about this.

Re:Node v7.5.0 Released by gsathya_hn on Friday February 03, 2017 @11:18AM (#13560834)
Native promises and bluebird use different microtask queues on Node which makes interop incorrect (which is also one of the reasons why bluebird is ES6 spec non compliant). See https://gist.github.com/anonymous/9936c695f2c29e79a0c1a0dec8...

This might lead to subtle ordering bugs, I would recommend against using both together.

Using bluebird and native promises together may result in slower performance because V8 has fast paths for native promises which fail for bluebird.

Re:Node v7.5.0 Released by Macha on Friday February 03, 2017 @01:07PM (#13561800)
If the ordering is important, then you should be using

    a()
    .then(()=>b())
    .then(()=>c())
Assuming anything about the completion order of:

    a():
    b();
    c();
Where a, b and c are async tasks of any kind (be they classic old callbacks, Promises, Observables, whatever) is just asking for trouble. The whole point is that you don't care about when it finishes, you just want to know that it has finished. If the order is that important and you don't want to handle managing it, you should just write standard synchronous code.
Re:Node v7.5.0 Released by randallsquared on Friday February 03, 2017 @11:47AM (#13561094)
In what way is this incorrect? Those are three separate promises; no code can be correct that depends on whether one resolve() call finishes before an independently started resolve() call, right?
Re:Node v7.5.0 Released by SparkyMcUnicorn on Friday February 03, 2017 @10:35AM (#13560461)
Bluebird is faster than native promises as well.

http://softwareengineering.stackexchange.com/questions/27877...

Re:Node v7.5.0 Released by jodoherty on Saturday February 04, 2017 @09:11AM (#13567011)
I've used the `co` library at work to extensively trim down and simplify a lot of server-side Node.js code recently. It's worked out very well compared to the very Promise heavy code we had before, especially since so many database libraries now make use of promises. I would highly recommend it if you're going to be stuck using Node v6 for the next 2-4 years.

On the front-end, we're using TypeScript 2.1.5, so we've been able to make good use of async/await there. The only tricky parts are dealing with AngularJS, due to the way it handles change detection via scope digests.

Both solutions still require the use and understanding of ES6 promises, especially when dealing with code that only works with callbacks.

Re:Node v7.5.0 Released by devwastaken on Friday February 03, 2017 @10:49AM (#13560584)
Promises don't cover a lot of scenarios in async usage. When building some node apps, especially toolage, doing everything async just isn't possible, or is a lot more work for little benefit. The second you use a promise, anywhere, working with its output is now also async, you cannot 'wait' for it. I don't know if async/await actually does this or not, but until that functionality exists in JS, working with node, for me, is a pile of junk code, especially when libraries force you to use them asynchronously.
Re:Node v7.5.0 Released by AgentME on Friday February 03, 2017 @02:32PM (#13562525)
>working with its output is now also async, you cannot 'wait' for it ... , but until that functionality exists in JS, working with node, for me, is a pile of junk code, especially when libraries force you to use them asynchronously

Asynchronicity is something that Node makes explicit and necessary for I/O, and it's practically a core principle of Node and javascript. If you try to avoid asynchronicity entirely, then you're not going to get much further than you would if you tried to avoid classes in Java.

Async/await is a very useful syntax sugar to working with promises, but it's not built to let you ignore asynchronicity entirely.

Re:Node v7.5.0 Released by grepthisab on Friday February 03, 2017 @07:18AM (#13559264)
My understanding was that Node didn't support native ES6, is that no longer the case? Or are you talking about using a transpiler?
Re:Node v7.5.0 Released by quarterto on Friday February 03, 2017 @07:20AM (#13559278)
Node has supported all of ES6 except modules since 6.0.
Re:Node v7.5.0 Released by grepthisab on Friday February 03, 2017 @08:39AM (#13559679)
Ah, this explains it. I tried to use an import statement a couple days ago and it failed, so I assumed it still wasn't supporting ES6. Good to know!
Re:Node v7.5.0 Released by Klathmon on Friday February 03, 2017 @07:20AM (#13559275)
Node supports all of ES2015 and ES2016 and will soon support ES2017 in the next few months or so.

So yeah, for most things you don't need to use a transpiler.

Re:Node v7.5.0 Released by lojack on Friday February 03, 2017 @07:57AM (#13559450)
A year or two ago that was the case. Then io.js forked, implemented a bunch of features and later was brought back into the NodeJS fold. Since then, Node has been a lot more proactive about keeping up with newer specs.
Re:Node v7.5.0 Released by MehdiHK on Friday February 03, 2017 @10:40AM (#13560506)
You can always check here: http://node.green
Re:Node v7.5.0 Released by wereHamster on Friday February 03, 2017 @03:01PM (#13562774)
I don't see ES6 modules (import/export keywords etc) in that list. Where is that feature hiding?
Re:Node v7.5.0 Released by charrondev on Saturday February 04, 2017 @02:36PM (#13568588)
The loader spec hasn't been completed yet so there is no way to load properly modules in es2015, es2016, or es2017 as far as I know.
Re:Node v7.5.0 Released by wereHamster on Sunday February 05, 2017 @11:44AM (#13573154)
But the syntax is in es2015. So that feature should be marked as NO in those tables.
Re:Node v7.5.0 Released by sfilargi on Friday February 03, 2017 @01:24PM (#13561934)
Grandparent: "lack of a definitive library"

Parent: "You have a number of ways"

Do you see the irony?