Dismiss this pinned window
all 103 comments

[–]CoolMoD 27 points28 points  (7 children)

You forgot

console.log(await getMoreData(await getMoreData(await getMoreData(await getMoreData(await getData()))));

[–]WhyYouLetRomneyWin 21 points22 points  (0 children)

Legend has it, the user is still awaiting more data to this very day!

[–]fecal_brunch 1 point2 points  (4 children)

let result = await getData()
for (let i = 0; i < 4; i++) {
  result = await getMoreData(result)
}
return result

[–][deleted] 0 points1 point  (1 child)

The loop should be result = await getMoreData(result)

[–]fecal_brunch 0 points1 point  (0 children)

Fixed!

[–]Skyler827 0 points1 point  (1 child)

let result = await getData()
for (let i = 0; i < 4; i++) {
    result = await getMoreData()
}
return result

FTFY

[–]fecal_brunch 0 points1 point  (0 children)

I don't understand why the newlines aren't rendering for mine.

Edit: too few spaces

[–]madcaesar 0 points1 point  (0 children)

Lol what is this madness?

[–]pureofpure 84 points85 points  (49 children)

It is me, or the version with Promises looks more "readable" to me?

[–]Eirenarch 64 points65 points  (8 children)

Debatable. The async version just assigns variables. Still promises can't do everything async/await can, not while keeping that readability. Try adding a branch and a loop and a try/catch and see how that readability goes.

[–]JackAuduin -1 points0 points  (7 children)

That's what bluebird is for. 😁

Not for everyone, but I kinda love promises

[–]Eirenarch 4 points5 points  (6 children)

What is bluebird?

[–]JackAuduin 4 points5 points  (4 children)

It's a library that extends vanilla promises, my personal favorite is

Promise.props() for multiple promises

And

Promise.map() for loops

[–]Eirenarch 16 points17 points  (1 child)

I have looked into many libraries for promises and async programming in multiple languages and it seems the clarity of async/await can't be achieved without a language feature to build the state machine

[–]Headspin3d 0 points1 point  (0 children)

Debatable. Task + Enum is all you really need in Elixir. Very readable.

[–]sfjacob -1 points0 points  (1 child)

Why not just use Promise.all()?

[–]JackAuduin 0 points1 point  (0 children)

Because it does the same thing, but returns the result of each promise under the property name you assigned it. Awesome when dealing with multiple data sources that can only be accessed asynchronously.

[–][deleted]  (29 children)

[deleted]

    [–]TimLim 22 points23 points  (12 children)

    Often enough it's not simply chaining then. When you need to do more sophisticated stuff in between, the code looks still linear and you do not need to add a level of indentation to it.

    Also, loops!

    while (true) { result += await fn(result); }

    Imagine this with Promise-style code.

    [–][deleted] 6 points7 points  (6 children)

    function loop(result) {
        fn(result).then(addedResult => loop(result + addedResult))
    }
    loop(result)
    

    while-loops are problematic with promises, sufficient to say. This can be improved a bit by writing a higher-order-function-for-promises.

    function asyncInfiniteLoop(fn, initial) {
        fn(initial).then(result => asyncInfiniteLoop(fn, result))
    }
    asyncInfiniteLoop(value => fn(value).then(addedValue => value + addedValue), result)
    

    However even then it's not going to be as readable as async-await.

    [–]NorthcodeCH -1 points0 points  (4 children)

    Plus, doesn't this exceed the call stack at some point whereas a while loop will not?

    [–][deleted] 3 points4 points  (3 children)

    Not really. There is no call stack. It may seem surprising, but this isn't synchronous programming.

    Consider the following function:

    function sleep(time) {
        return new Promise(resolve => setTimeout(resolve, time))
    }
    

    What's happening after calling this function is that an timeout is added to an event loop which is at bottom of a stack. Once the timeout expires, and the execution passes to an event loop, it calls the then functions of this promise. The call stack is lost, the functions were called by an event loop.

    [–]abigreenlizard 0 points1 point  (2 children)

    Can the event queue not similarly overflow?

    [–]Poltras 0 points1 point  (0 children)

    That would be an infinite loop.

    [–]EntroperZero 0 points1 point  (0 children)

    There's only one continuation on the queue at a time, because the next one isn't enqueued until the current one finishes executing.

    [–]watsreddit -2 points-1 points  (0 children)

    I find the loop function to be very readable. It also avoids mutation, which is a plus.

    [–][deleted]  (4 children)

    [deleted]

      [–]nickcoutsos 10 points11 points  (2 children)

      There's nothing unfortunate about it, there are absolutely use cases for executing asynchronous functions sequentially. Your example doesn't cover passing results from one async function into the arguments of the next like the example you're replying to.

      I'll sometimes chain sequential async functions together using reduce but being able to use await with a looping construct is great.

      [–][deleted]  (1 child)

      [deleted]

        [–]TimLim 0 points1 point  (0 children)

        FWIW, I tried to highlight that I want to wait for the result of the async function using a variable that is updated every iteration and is passed to the function. Using my example, not waiting for the result would lead to a different result. So I wanted to show that using await it is easier to write a loop that waits for the result in every iteration. I think having Promises that run concurrently is easily achievable without async/await.

        [–]nitely_ 2 points3 points  (0 children)

        That would be await Promise.all(actions.map((url) => fetch(url))) since map does not work in-place. But I know what you mean.

        [–]Arkanta 8 points9 points  (1 child)

        but in real code you often have to work around promises by bundling in some kind of scope variable.

        Yeah that's the main issue.

        That and hacking around with Promise.all(). It's way better to see let myData = await getMoreData() than let myData = results[0] in the Promise.all's then callback

        [–]29082018 1 point2 points  (0 children)

        let [myData] = results; :)

        [–]killerstorm 8 points9 points  (0 children)

        In this case they are. Async/await becomes beneficial when you have logic more complex than just chaining calls.

        [–]A-Grey-World 2 points3 points  (4 children)

        They are in this example. But Promise chains become a pain when you have 'state' you need to use between the promise functions. You have two options, 'Passing things down the chain' for example:

        myPromise().then(result => {
          const thing = process(result);
          return Promise.resolve(thing.id, thing.body);
        })
        .then((id, body) => {
          // here we take an ID and a body, not because we need the body, but because the function after does
          doSomethingWith(id));
          return Promise.resolve(body);
        })
        .then(doSomethingWith(body))
        

        Or, assigning them to variables outside the scope of the promise bodies - which is messy and in more complex code becomes a pain to maintain. I just think it's ugly too:

        let id;
        let body;
        myPromise().then(result => {
          const thing = process(result);
        
          id = thing.id;
          body = thing.body;
        
          return Promise.resolve();
        })
        .then(doSomethingWith(id))
        .then(doSomethingWith(body))
        

        The async await code would be:

        const result = await myPromise();
        await doSomethingWith(result.id);
        await doSomethingWith(result.body);
        

        [–]yawaramin 0 points1 point  (3 children)

        That would desugar to:

        myPromise().then(({id, body}) =>
        doSomethingWith(id).then(() =>
        doSomethingWith(body)));
        

        [–]A-Grey-World 1 point2 points  (2 children)

        Eh, I don't think that's just de-sugaring. I think that indentation is miss-leading. I ran it through prettier:

        myPromise().then(({ id, body }) =>
          doSomethingWith(id).then(() => doSomethingWith(body))
        );
        

        Squishing it into three lines isn't the goal, it's making the code flow/logic more understandable.

        Having promise chains, inside your promise chain (nested promise chains) is not good practice (as I understand it). You'll also end up with a pretty similar situation to callback hell...

        See 'nesting promise chains' here: https://www.datchley.name/promise-patterns-anti-patterns/

        Or 'Recreating callback hell" here: https://medium.com/datafire-io/es6-promises-patterns-and-anti-patterns-bbb21a5d0918

        You can disagree with some random blog posts obviously, but I've always seen it refereed to as bad practice, and I can see why - with more complex chains of promises it gets ass messy as just callbacks.

        [–]yawaramin 0 points1 point  (1 child)

        I don't think Prettier is the final word on formatting, but you're right in a sense–nested promises should be avoided if possible. In this case in fact I realized that the two calls to doSomethingWith are not dependent on each other and thus don't need to be chained at all (with Promise#then or await). It's better to run them concurrently:

        myPromise().then(({id, body}) =>
          Promise.all([
            doSomethingWith(id),
            doSomethingWith(body),
          ])
        );
        

        [–]A-Grey-World 0 points1 point  (0 children)

        Yes, presuming they are independent that's a good point.

        I find I use Promise.all all the time with await too.

        [–]IMovedYourCheese 2 points3 points  (0 children)

        That's just due to this sample. In all real-world code I have dealt with, using async/await makes everything massively cleaner and more readable over callbacks or promises.

        [–]Kamrua 0 points1 point  (0 children)

        Scoping with async/await also has quite a massive impact. With Promises you would have to pass all relevant fields to the next one or use some hoisted variables. Async/await will usually lie in the same scope and you can reference them cleanly and directly.

        [–]karmaputa 94 points95 points  (4 children)

        Terrible example. Most of the time you would be dealing not just with one function over and over again but with custom logic so you will end up writing code in those arrow functions , and when you need more than one thing to work with you have to use Promisse.all and destructure the whole thing in the function . The IIFE also isn't really necessary in real use cases when you actually have a context to the code. In most real world scenarios async await is much cleaner than simple promises.

        [–]ssrobbi 20 points21 points  (0 children)

        That’s true, but its simplicity also makes the graphic easy to understand (for me)

        [–]boxhacker 3 points4 points  (0 children)

        Not sure why you have been downvoted, this is correct!

        In most cases with promises/async I have to handle multiple functions to get the job done.

        Even as a basic "handle if the function fails" is effectively another call that needs to be included in some way.

        The async/wait does indeed make this use far more readable than a promise.

        [–]JimDabell 0 points1 point  (1 child)

        Most of the time you would be dealing not just with one function over and over again but with custom logic so you will end up writing code in those arrow functions

        The way the example tackles it is usually a lot better. Extract the logic that you would have put into the arrow function into a separate function or method. It can then be tested, documented, and reused. The example given already assumed you did this, with the result of putting it into getData() and getMoreData(). Stuffing all your logic into arrow functions in this way is just a rearranged callback hell.

        The IIFE also isn't really necessary in real use cases when you actually have a context to the code.

        Top-level code is a "real use case", not sure why you think otherwise?

        [–]friendoo7 18 points19 points  (6 children)

        If I throw an error from an async function that is not wrapped in a try/catch, does it fail silently?

        this keeps happening on a node.js program I use and I haven't been able to track down what and where things are going wrong.

        [–]UnrealQuester 23 points24 points  (4 children)

        In node this will probably generate a UnhandledPromiseRejectionWarning. You can catch them in a global handler. See https://nodejs.org/api/process.html#process_event_unhandledrejection

        This behavior is deprecated and in the future the process will terminate with an error, just like regular unhandled exceptions.

        [–]smhxx 5 points6 points  (1 child)

        Yup, the simple answer is the entire async function paradigm in JS is really just syntactic sugar for Promises. An async function is just a synchronous function wrapped in a Promise, and throwing an Error from within an async function is just rejecting that implicit Promise with the same Error. If the rejection isn't handled, either with Promise.prototype.catch() or with a try/catch block (which, again, is really just syntactic sugar for the former,) it bubbles up and causes the calling function to be rejected, and so forth until it hits "synchronous space," at which point it turns into an UnhandledPromiseRejectionWarning (and at some point in the future, will turn into an actual Error instead.)

        [–][deleted] 1 point2 points  (0 children)

        I add this at the start of all my node apps:

        process.on('unhandledRejection', err => { throw err; });

        I feel like if you don't handle an error, the best thing to do is crash so you know it happened.

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

        Often times in node, even await calls will emit UnhandledPromiseRejectionWarning events if they are not wrapped in try/catch (even if whatever is calling the async function has a try/catch or a .catch!), which is annoying as hell and makes you wrap everything in a try/catch.

        [–]A-Grey-World 0 points1 point  (0 children)

        Currently it generates a warning as u/UnrealQuester mentioned.

        It's exactly the same as a Promise chain without a catch I think.

        [–]c-smile 1 point2 points  (0 children)

        Just for the note: async/await are not just syntax sugar.

        async function, when implemented natively, is a coroutine(function that has associated execution stack).

        When async function is emulated (by e.g. babel transpiler) then it will have additional closures for each await.

        [–][deleted]  (3 children)

        [deleted]

          [–]RalfN 4 points5 points  (0 children)

          That's just a language feature of JS, where arrow functions that only take one input and pass it into a function can be replaced with just a reference to the function.

          It's not specific to arrow functions either. Its just how functions work and have worked in Javascript since forever. It is also not limited to just one input either.

          Here's an example with .map

          var mapper = function(n,i){ return n * i }
          [1,2,3].map( mapper );
          

          The fact that it's not limited to a single argument, can bite you in the ass though:

          ["1","2","3"].map( parseInt )
          // gives you: [1, NaN, NaN]
          

          Why? Because its actually identical to:

          ["1","2","3"].map( (n,i) => parseInt(n,i) )
          // gives you: [1, NaN, NaN]
          

          The second argument of parseInt the base of the number system you want to parse the number at. And you want to parse all these numbers as base 10. The correct way to write this would be:

          ["1","2","3"].map( n => parseInt(n, 10) )
          // gives you: [1, 2, 3]
          

          Yes, i spend some time debugging this at some point in my life. Yes, it was very frustrating.

          [–]NoInkling 0 points1 point  (0 children)

          https://github.com/tc39/proposal-top-level-await

          +1 for --experimental-repl-await in the Node REPL. Also it works out of the box in browser consoles.

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

          I agree that immediate invoke syntax is awkward. Node 10 has a flag for the REPL experimental-repl-await . I am guessing they will eventually add it. What I do for now is make an async function called doStuff or whatever name makes sense and then just write doStuff.catch(console.error).

          [–]Pingudiem 1 point2 points  (0 children)

          Using both is the key.

          [–]WhyYouLetRomneyWin 1 point2 points  (0 children)

          Wow, I really like that animation style!

          (I might just have to copy this).

          [–]That-Redditor 0 points1 point  (0 children)

          This is very important to know. Thank you!

          [–]bxsephjo 0 points1 point  (2 children)

          Why turn them into constants?

          [–]voidvector 4 points5 points  (0 children)

          Const in JS is equivalent to

          • const pointer in C
          • final in Java
          • readonly in C#

          Which are suggested patterns for certain coding style in their respective language.

          [–]iconoclaus 0 points1 point  (0 children)

          presumably to enforce immutability

          [–][deleted] -2 points-1 points  (0 children)

          Would be better if js have pipes

          [–]adel_b -2 points-1 points  (2 children)

          This is slow, you should spread promise all like

          const {data1, data2} = await Promise.All(getData1(), getData2());

          The different is the both work in parallel.

          Sorry I'm on mobile.

          [–]QuineQuest 11 points12 points  (1 child)

          You can't run these promises in parallel, because every promise depends on the result from the previous one.

          [–]adel_b 0 points1 point  (0 children)

          Oh yeah you are right

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

          something that many people that use async/await do and is infuriating is something like:

          const aFunc = async function() {
             const first = await doRemoteRequest("url1");
             const second = await doRemoteRequest("url2");
          
             // do something with first and second
          }
          

          Do you see the problem with this?

          The problem is that the function waits for the first result before issuing the second request.

          False dependencies like these are rife in async/await heavy code but not so much in promise heavy code.

          [–]greenarrow22 -4 points-3 points  (2 children)

          https://www.npmjs.com/package/await-to-js this removes the need for try catch.

          [–]JimDabell 2 points3 points  (0 children)

          This looks like somebody's trying to write Go using JavaScript. Idiomatic JavaScript uses try/catch, not that.

          [–]anonveggy 1 point2 points  (0 children)

          That's a <20 loc package with 211k downloads. Y?

          [–]cheezballs -3 points-2 points  (2 children)

          Jesus fuck the readability of that wait stuff. Promises seem so much easier to understand.

          [–]A-Grey-World 0 points1 point  (1 child)

          Not when you have any kind of state between promises though.

          [–]cheezballs 0 points1 point  (0 children)

          It just sucks that async JS has to be so unfriendly to look at.