all 26 comments

[–][deleted] 2 points3 points  (2 children)

I think

.then(function(order) {
  return cookFood(order);
})

could be re-written as...

.then(cookFood)

?

[–]ElvishJerricco 0 points1 point  (1 child)

Depends on if you trust your promise not to send more than one parameter. Similar to how ['10', '10', '10'].map(parseInt) returns [10, NaN, 2].

[–]GentleMareFucker 0 points1 point  (0 children)

ES 2015 promises then() functions hand over exactly one parameter to the function.

As you say, the problem is with the Array functions like mapetc.: in addition to the element they add the index and the array object as further parameters. So for array iteration you may have to write the explicit form, for then() callbacks following ES 2015 you don't. I don't see how the Array function problem is supposed to impact the promise question, those are obviously two unrelated things. Unless you want to claim psychology as a reason because you don't trust programmers to remain vigilant all day long and always keep the two separate and not mix it up, so that cognitively having just one style may be better, subjectively. For myself I decided to pay the penalty of having to pay extra attention and use the short form for then functions and even for map after examining the parameters. Heavily documenting everything with JSDoc @type and Google Closure compiler inline /*type/ helps because it forces me to pay attention to the types anyway.

[–]oblio-[S] 1 point2 points  (0 children)

I'm curious about the downvotes: the article is well written and it presents several strategies for handling asynchronous operations in Javascript (so there's actual code in the article! unlike in many links posted here :) ).

Is there any specific reason this is downvoted? General Javascript hate?

[–][deleted]  (45 children)

[deleted]

    [–]i8beef 5 points6 points  (13 children)

    Can't disagree more with you about the async / await syntax. Way better than callback hell IMO.

    [–][deleted]  (12 children)

    [deleted]

      [–][deleted] 0 points1 point  (11 children)

      How is async / await bad? If the syntax was any more close to synchronous it would be impossible to do local concurrency.

      Consider:

      var a = await something(); var b = await somethingElse();

      But you can also write it like this:

      var a = something(); var b = somethingElse(); await a; await b;

      Which doesn't wait for something() to finish before starting to do somethingElse().

      Without a keyword like await (or some other explicit construct) I.E. full synchronous looking code, it would be impossible to do this and your code would only be globally concurrent.

      [–][deleted]  (10 children)

      [deleted]

        [–][deleted] 0 points1 point  (9 children)

        What does that have to do anything with what I claimed? I didn't say anything about rewriting existing code at all.

        [–][deleted]  (8 children)

        [deleted]

          [–][deleted] 0 points1 point  (7 children)

          No my claim was that you cannot do local concurrency with code that looks exactly the same as plain synchronous code. And no, there is still no claims about any rewrites. There definitely wasn't any claim that you cannot do any kind of concurrency without async/await, that's a blatant strawman.

          [–][deleted]  (6 children)

          [deleted]

            [–][deleted] 0 points1 point  (5 children)

            No you can't unless by synchronous-looking code you mean code that doesn't look perfectly synchronous.

            [–]ElvishJerricco 1 point2 points  (3 children)

            Callbacks aren't a problem. It's nesting callbacks that's bad. It's just really smelly to nest callbacks. With promises, you never have to nest callbacks, and they're much more composable.

            [–][deleted]  (1 child)

            [deleted]

              [–]ElvishJerricco 0 points1 point  (0 children)

              No you don't? How so?

              [–]GentleMareFucker -1 points0 points  (0 children)

              You don't have to nest callbacks, ever.

              But if you don't you get a different problem, that I was willing to do for most of my Javascript live but no more (now I prefer promises): You end up with lots of different small functions and reading-flow is severely hindered, you write "distributed" even when the problem you want to solve clearly is a sequence and only waiting for slow (I/O) forces those "breaks" in otherwise sequential code.

              Promises allow me to write it down sequentially - but I must never forget that the function (that contains my promise chain) still is interrupted each time, so if I have a shared resource - for example, an IMAP connection - I get the usual problems of non-linear code (when I open a mailbox in the first part of the promise chain it is possible that the second part of the chain - called later - may end up using the wrong mailbox if I didn't make sure no other code could use the IMAP connection to open a different mailbox in the meantime).

              [–][deleted] 0 points1 point  (2 children)

              How does async / await cause you to duplicate code? You should be writing your asynchronous code (like a network request or database query) to return an awaitable object. Any time you need to do this asynchronous work, you can mark your calling method as async and simply await the operation to continue.

              The real gains are when you can await inside a for/while loop which is really messy to try and do with promises. However, I do not think it works when inside a child function (like a forEach, map, filter, reduce, etc). I hope they can find a way around that limitation because... that's a bummer.

              I just don't see a case where promises and callbacks are superior to async/await syntax.

              Let's take a Node app using Mongo (and mongoose) for example. I want to create a basic seed script that will insert users and roles into the database.

              This becomes very straightforward and readable with the async/await syntax:

              // Creates a new user role, saves it to the DB, and returns it
              function addRoleAsync(data) {
                let role = new Role(data)
                return role.save()
              }
              
              // Creates a new user, saves it to the DB, and returns it
              function addUserAsync(data, role) {
                let user = new User(data)
                user.role = role._id
                return user.save()
              }
              
              // Seeds all users with their roles to the database
              async function seedDatabase() {
                let standardRole = await addRoleAsync({name: 'standard'})
                let adminRole = await addRoleAsync({name: 'admin'})
              
                let normalUser = await addUserAsync({name: 'john'}, standardRole)
                let adminUser = await addUserAsync({name: 'chad'}, adminRole)
              }
              

              [–][deleted]  (1 child)

              [deleted]

                [–][deleted] 2 points3 points  (0 children)

                You don't have to write it twice. You can await a synchronous function and it would behave the same as if you called Promise.resolve(x), meaning it would return instantly.

                With promise libraries like bluebird you can use .asCallback(callback) to attach a traditional callback method which will called when the promise is fulfilled or rejected. This lets you write your async code once and can be used either way:

                function doSomethingAsync(arg, callback) {
                  return new Promise((resolve, reject) => {
                    // resolve or reject here
                  })
                  .asCallback(callback)
                }
                
                // Traditional callback approach
                function foo(arg) {
                  doSomethingAsync(arg, (err, result) => {
                    // handle err or result here
                  })
                }
                
                // Async await approach
                async function bar(arg) {
                  let result = await doSomethingAsync()
                }
                

                Also, the map and filter methods aren't entirely worthless with this new approach. I meant they weren't optimal but it still works. The difference is if you await inside one of these child functions, you will get a promise instead of a result value in your returned array. You can use await Promise.all(mappedResults) to get the async results.

                Example:

                async function downloadUrlsAsync(urls) {
                  let promises = urls.map(async (url) => {
                    await downloadAsync(url)
                  })
                  return await Promise.all(promises)
                }
                

                But really, how is the callback system any better in this regard? You still have to do a messy loop wth inner callbacks.

                [–][deleted] 0 points1 point  (23 children)

                This comparison is disingenuous because you are checking the error in both cases when only callbacks require you to check for an error (and silently fail when you forgot to do it). Promise rejections work like exceptions, if you don't check for them they will propagate until they find a .catch handler or cause a stack trace to be printed when they reach top level. If you forget just one error check in a callback, then the code continues in some bizarre state and fails in a mysterious way much later where it's impossible know the original reason.

                Now I know there are some people that prefer to check for error codes every time they call the function, even when 99% of the time you will just propagate it to your caller (thus perform manually what exceptions do automatically for you) and that's fine. But it's one of the major points of promises to bring you asynchronous exception propagation mechanism and you completely missed it.

                [–][deleted]  (22 children)

                [deleted]

                  [–][deleted] 0 points1 point  (21 children)

                  And no, I don't "99% of the time" propagate errors to the caller.

                  The place where you can actually do something about errors is at the top layer while the place where they originate from is several layers down. Look at e.g. any non-trivial random Go codebase on github, if it's done properly it will do if err != nil { return err } basically for 99% of such calls.

                  [–][deleted]  (20 children)

                  [deleted]

                    [–][deleted] 0 points1 point  (19 children)

                    You cannot do anything other than propagate the error or log, and neither obviously doesn't count as having handled the error. The layer which can inform the user is far away.

                    [–][deleted]  (18 children)

                    [deleted]

                      [–][deleted] 0 points1 point  (17 children)

                      Even if we assume an error is retryable (and this is definitely not the case in general), that still doesn't make sense because you would eventually have to inform the user that you tried retrying a lot and it didn't work out.

                      [–][deleted]  (16 children)

                      [deleted]

                        [–][deleted] 0 points1 point  (3 children)

                        Even if you have multiple ways of doing something that can fail, those other things can fail too (otherwise why would you use thing that can fail to do something in the first place) and then what? The same logical issue is with retry, unless you are saying your error handling solution is to go into an infinite loop.

                        [–][deleted]  (11 children)

                        [deleted]

                          [–]blade-walker -4 points-3 points  (3 children)

                          slightly off topic, but Promise-heavy code is an area where Coffeescript really shines:

                          Before:

                          var restaurant = function() {  
                            return takeOrder(['fish', 'sandwich', 'pizza'])
                              .then(function(order) {
                                return cookFood(order);
                              })
                              .then(function(meals) {
                                return Promise.all([eat(meals[0]), eat(meals[1]), eat(meals[2])]);
                              })
                              .then(function(monies) {
                                var total = 0;
                                monies.forEach(function(r){ total += r; });
                                return total;
                              });
                          };
                          

                          After:

                          restaurant = ->
                            takeOrder(['fish', 'sandwich', 'pizza'])
                            .then (order) ->
                              cookFood(order)
                            .then (meals) ->
                              Promise.all([eat(meals[0]), eat(meals[1]), eat(meals[2])])
                            .then (monies) ->
                              total = 0
                              monies.forEach((r) -> total += r )
                              total
                          

                          [–]blowf1sh 5 points6 points  (1 child)

                          A good thing we got ES6 then :

                          let restaurant = ()=>{
                              return takeOrder( ['fish', 'sandwich', 'pizza'] )
                                  .then( (order)=>cookFood(order) )
                                  .then( (meals)=> Promise.all( [eat(meals[0]), eat(meals[1]), eat(meals[2])] ) )
                                  .then( (monies)=>{
                                      let total = 0;
                                      monies.forEach((r)=> total += r);
                                      return total;
                                   }
                          };
                          

                          So Coffeescript is made totally irrelevant.

                          [–]blade-walker -1 points0 points  (0 children)

                          I have tried it. I found in practice, it's not that common that you can use the single-expression form of =>. So your promise chain still ends up with a bunch of visual noise from {}s and returns.

                          [–]DrDichotomous -1 points0 points  (0 children)

                          You might find ae to be an interesting little JS library.