46 comments

[ 3.3 ms ] story [ 120 ms ] thread
There should be some review process for these .christmas articles. I get it - creating tons of articles quickly is hard, but the core message should not be incorrect.

I tried to look up a github reference on the page, so I could send a PR to fix the linked article, but it is not available.

We decided to keep the content in a private repo until every article is posted.

If you could share what you found incorrect, we are happy to update the article accordingly

> "thus forcing the code to be synchronous"

This (incorrect) message is then repeated again ("and act synchronously").

There are some other - minor - issues with rest of the article. I.e. asyncFunction and someFunction are not equivalent and as such can cause confusion (the same issue is in waitAndCheck vs chain.

Update the article based on your feedback.

Additionally, we included a link to the "Top level await" proposal. Currently stage 3.

I appreciate that the author is trying to illustrate something with an intentionally contrived example, but there is really no need for "await" in his "chain()" function:

  function chain () {
    (function loop (i) {
        if (i > 5) {
            // set state to failed
            return;
        }

        check().then((result) => {
            if (!result) {
                return loop(i + 1);
            }

            // set state to finished

        }).catch((error) => {
            // set state to failed
        });
    }(0));
  }
This is one of my pet peeve with proponents of promises actually. They come up with convoluted examples to argue against callbacks but it's not the callbacks that are a problem, but the person writing the code.

Actually, I would argue that promises really made no sense until "await" became available because they introduce extra complexity and (small) performance penalty for no added benefit. Worse, it encourages less experienced developers to write code in a serial manner in situations where this is not necessary (a much bigger performance problem).

Even with "await", the only time when it is really useful is when there have to be a several asynchronous operations that must happen serially because they use the result of the previous call. It depends on your field of work, of course, but in my experience these situations are really not that common.

Some time ago I wrote an article comparing performance of callbacks vs async/await but it is also an example to show that callback code does not have to be more tedious to write:

https://gir.me.uk/posts/node-8-async-await-performance-test....

> This is one of my pet peeve with proponents of promises actually. They come up with convoluted examples to argue against callbacks but it's not the callbacks that are a problem, but the person writing the code.

While I agree the author’s examples are convoluted, the async-await code is much easier to read, write, and validate. Why bother tracking state and having to keep tracking of JS lexical scoping when you can write "boring" code that will be just as fast for the tiny iteration counts you'll be dealing with?

> Even with "await", the only time when it is really useful is when there have to be a several asynchronous operations that must happen serially because they use the result of the previous call. It depends on your field of work, but in my experience these situations are really no that common.

Concurrent tasks are more pleasant with await as you can combine it with Promise.all(...) to get efficiency and legibility:

    async function doStuff() {
        // Fetch things concurrently
        const [
            foos,
            bars,
            bazs,
        ] = await Promise.all([
            getFoos(),
            getBars(),
            getBazs(),
        ]);
        // Do stuff with them...
    }
> the async-await code is much easier to read, write, and validate.

With the exception of necessarily serial code that I referred to, this statement is not true in my experience. Your "legible" example is only legible because you are not doing anything actually important like error handling and logging. In the real world each call may need different logging or even error handling logic and that is when promises really turn into a rats nest.

Validation, i.e. tests, are really the same regardless which approach you are using.

I do agree that async/await made promises actually usable, as I said above.

For running a bunch of asynchronous functions that do the same thing the semaphore + collector pattern works just fine and is not much more verbose.

>Your "legible" example is only legible because you are not doing anything actually important like error handling and logging.

    async function doStuff() {
      try {
        // Fetch things concurrently
        const [
          foos,
          bars,
          bazs,
        ] = await Promise.all([
          getFoos().then((res) => {
            console.log(`getFoos done with ${res}`
            return res
          })),
          getBars(),
          getBazs()
            .catch((err) => {
              // handle only the error in getBazs
            }),
        ]);
        // Do stuff with them...
      } catch (err) {
        // handle any errors
      }
    }

it still isn't what i'd call "pretty" code, but it's simpler for me to read and comprehend than doing most of those things without async/await or Promise.all or other helpers like that.
I was about to write up that exact example of the nested handlers.
I actually love slinging promises like that, but I tend to avoid it in most cases as there are MANY devs that would be somewhat rightfully frustrated at the code I just wrote.

As always, who you are working with matters a LOT, and if I were on a team where a good portion of the devs weren't comfortable with that style of code, then I'd use more verbose ways of doing it that are more inline with what they are expecting.

But on the rare occasion that the complexity necessitates the advanced promise stuff, or in jobs (like my current one) where all of us devs are super comfortable with promises being used like this, then it's a dream to work with them!

(see below)
But those 2 examples aren't doing the same thing.

My Promise.all example will fire off the getFoos, getBars, and getBazs requests all at the same time, and then do things at different moments with the results in some cases. Yours will do them serially like this:

getFoos -> wait for it to resolve -> run getBars -> wait for it to resolve -> run getBazs -> wait for it to resolve -> run doStuff

And if individually handled errors is what you are after ala go, then this also does it while preserving the parallelism:

    async function doStuff() {
      // Fetch things concurrently
      const [
        foos,
        bars,
        bazs,
      ] = await Promise.all([
        getFoos().catch((err) => {
            // handle only the error in getFoos
          }),
        getBars().catch((err) => {
            // handle only the error in getBars
          }),
        getBazs().catch((err) => {
            // handle only the error in getBazs
          }),
      ]);
      // Do stuff with them...
    }


And I'm not suggesting we always use promises in every area, but that for some things they are the perfect tool for the job. There are some things that promises are really bad at that callbacks do really well. One example is progress systems.

A callback can be called multiple times over the course of an async call with percentage it's completed every call. A promise is one-and-done, and that's a big issue in many areas. But that's why callbacks aren't really "deprecated" as much as they are relegated to doing what they do best, while leaving things that Promises and async/await do best to them.

You are right - got mixed up in the responses - serves me right for trying to do too many things async!

I would do it like this (if I didn't have access to "async/await" which is sadly the case for me in Node sometimes):

  function doStuff (foos, bars, bazs, callback) {
    // do stuff
  }

  function getStuff (callback) {
    let foos, bars, bazs;
    let semaphore = 3;

    function collector () {
        if (--semaphore) {
            return;
        }

        doStuff(foo, bars, bazs, callback);
    }

    getFoos((err, res) => {
        if (err) {
            return callback(err);
        }

        foos = res;
        collector();
    });

    getBars((err, res) => {
        if (err) {
            return callback(err);
        }

        bars = res;
        collector();
    });

    getBazs((err, res) => {
        if (err) {
            return callback(err);
        }

        bazs = res;
        collector();
    });
  }
It is more lines of code but is boringly simple to read and allows for fully custom error handling (an example of the semaphore + collector pattern I mentioned below in another comment).

The custom error handling would be in the individual "get" functions which allows for easier testing.

The semaphore in the collector is the most tricky part and I would possibly replace it here with an "if" to check that all the variables have a value - depends on what sort of data I could get back.

well IMO that is much more difficult to read for me.

Promises are nice because they have a single purpose, to resolve or reject a promised value. So when I see them, I can pretty much instantly understand what it's doing. `Promise.all` tells me that it's waiting for all of them to resolve at once, `Promise.race` tells me that the result will be the first of them to resolve or reject.

With callbacks, everything is "custom". So looking at your example I'd need to fully read the function to get an understanding of what it's doing, vs just knowing that Promise.all means "wait for them all and then resolve".

Granted, if where you are working tends to avoid promises or heavily uses a language like golang where your style is much more common, then I actually completely agree with you that the callback version is going to be more maintainable, readable, and easier to work with in your team.

But if/when your org/team collectively learns the "shorthand" that promises can provide, IMO they become much simpler to read and understand in most cases. It's basically a way of providing structure to really common patterns like yours, so that the actual structure fades into the background and the important stuff (what you are trying to do) becomes the only focus.

In other words it's syntactic sugar. With all the benefits and drawbacks that any syntactic sugar provides.

I agree with you now that "async/await" is available (as I said in my article that I originally linked to). And thank you for providing a second use case to the one I listed in the article - it paid for all the time I wasted on HN today with this thread. ;)

The little bit of magic that makes your code sane is the "await" in front of "Promise.all()" and makes it more elegant than the callback version.

>The little bit of magic that makes your code sane is the "await" in front of "Promise.all()" and makes it more elegant than the callback version.

I'd actually say that it's the `Promise.all` that makes it sane, the await could be replaced by a `.then` and be functionally the same.

Actually it might be even easier to read to someone who doesn't live and breathe javascript as it will keep the program flowing top-to-bottom:

    Promise.all([a(), b(), c()]).then(([a, b, c]) => {
      // do stuff...
    })
As with most things javascript, there are 9 different ways of doing it, and 3 of them will shoot you in the face...
Wow that is ... beautiful. :))

And if the individual functions have the custom error logic inside then a simple ".error(callback)" at the end it will just work.

> It depends on your field of work, of course, but in my experience these situations are really not that common.

Very common in my field. A single function that loads a file, parses metadata, then transforms the binary data into a different format can consist of 2-4 async calls that need to be executed in order. Await is incredibly helpful here, and makes code much more readable compared to the chain of then statements we previously used.

Even something simple as the fetch API to load a json file consists of two async calls in a row:

    const response = await fetch('http://example.com/movies.json');
    const myJson = await response.json();
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/U...
And your error handling? ;)

  fetch((err, res) => {
    if (err) {
      // yay - error handling! :)
    }

    const myJson = res.json();
    // do stuff
  }
What's so unreadable about this? Even if it is 4 calls it would still be readable because if you have any sizeable amount of logic you should be refactoring it into several functions anyway with the above function gluing them together. Much easier to write tests for too!
As far as I can tell, json() also needs to be treated as a promise, and fetch doesn't take a callback as an argument Wouldn't the correct non-await version look like this?

    fetch(url).then( response => {
        response.json().then( json => {
            // do stuff
        });
    });
This is already much worse then the await version, without any error handling yet. And if I need to call another promise in order, I'd need yet another level of callback. Instead of two easily intelligible lines that could be wrapped into a single try/catch, we now have 4 lines and two new scopes/levels.

Why would I ever use this over await?

you can chain them and avoid the nesting. Anything a .then handler returns becomes the value of the next one like this:

    fetch(url)
      .then((response) => response.json())
      .then((json) => {
        // do stuff
      })
Promises will wait until resolved, normal values will call the next handler "right away" (there's some nuance here and some edge cases about what "right away" means, but for the most part you never need to think about that)

And if you want to do more things and handle errors, it becomes pretty simple as well:

    fetch(url)
      .then((response) => response.json())
      .then((json) => {
        if (!json.user.id) {
          throw new Error('user id not found')
        } else {
          return db.sql('SELECT * from users where id = $1', [json.user.id])
        }
      }).then((user) => {
        return response.send(user)
      }).catch((err) => {
        // any error thrown at any point during the chain will trigger this catch
        return response.error(err)
      })
I still completely agree that async/await is still better in this case, but then throw some more wrenches into the situation like wanting to handle multiple promises at a time and you start to see where using "raw promises" really comes in handy. Like this:

    try {
      const res = await fetch(url)
      const json = await res.json()

      if (!json.user.id) {
        throw new Error('user id not found')
      }

      const [ userObj, userAuthLevel, someOtherStuff] = await Promise.all([
        db.sql('SELECT * from users where id = $1', [json.user.id]),
        db.sql('SELECT * from otherStuff where userId = $1', [json.user.id]),
        fetch('https://other.stuff/and/things')
      ])

      return response.send({
        userObj,
        userAuthLevel,
        someOtherStuff
      })
    } catch (err) {
      return response.error(err)
    }
or say there's an expensive call that you can start BEFORE the first fetch, but still need to wait on later (ignoring most other stuff for simplicity):

    // notice there's no await...
    // we can kick off the request now, but not wait for the result until later
    const someOtherStuffPromise = fetch('https://other.stuff/and/things') 

    const res = await fetch(url)
    const json = await res.json()

    // do other things here

    // finally wait for the promise to resolve here.
    const someOtherStuff = await someOtherStuffPromise
In your last example is `someOtherUserRelatedStuffPromise` on the last line supposed to be `someOtherStuffPromise` from the third line?

Either way, there's a very interested construct with promises I didn't realize was possible, but makes sense now that I see it. I never thought about awaiting an initiated promise at any point later on.

Parent's code with callbacks would be more like this:

  fetch((err, res) => {
    if (err) {
      // yay - error handling! :)
    }

    res.json((err, myJson) => {
      if (err) {
        // oh no - another error handler :(
      }
      
      // do stuff
    });
  }
fetch.json is async as well. With await the try / catch would just be around both await calls.
Yes this is true, as long as your error handling is the same for all calls (not necessarily the case, especially if logging is involved).

I don't actually have a problem with the callback version because it is nice and explicit. But again, I did originally say say that "async/await" was actually a valid use case in this necessarily serial scenario (and is exactly what I do in my article that I linked to). ;)

That said, if I see a chain of serial calls more than a few levels deep it is a code smell that indicates refactoring is necessary. There really is no reason to write code like that unless it's very simple logic. This is why I have never had a problem with "callback hell" even though I write code that is heavily asynchronous (APIs, WebSocket, pub/sub etc.)

I agree. Promises solve superficial aesthetic problems that only beginners trip on and unnecessarily add dangerous hidden state and corner cases.

You can further simplify that chain() function by removing promises altogether:

    function chain () {
        (function loop (i) {
            if (i > 5) {
               // set state to failed
                return;
            }
            check((err,result) => {
                if(err){//set state to failed
                    return;}
                if (!result) {
                    return loop(i + 1);
                }
                // set state to finished
            });
        }(0));
    }
Callbacks are much simpler and less error prone:

https://medium.com/@b.essiambre/continuation-passing-style-p...

CPS is a very bad idea in JS at present.

CPS in scheme works because proper tail call optimization exists, so your call stack doesn't explode. In current JS, the only way to make that work is performing a nextTick or setTimeout on every single function which trashes performance of simple things due to exiting your code back to the event loop all the time.

<stands on soapbox>

There's a special place in hell reserved for companies that deviate from standards because of petty disagreements. Proper tail calls have been a part of the JS spec for 5 versions now. They are implemented in Safari and in popular embedded engines like duktape. MS and Firefox refused to implement. They were in v8 behind a flag, but were removed.

v8 devs pitched a fit about stack traces disappearing (even though they disappear across the event loop anyway). The webkit team showed that Chicken scheme's shadow stack works just fine and it's been in production for a long time now (without any issues I'd add). Instead, they insisted that they wanted to revise the standard to require marking tail-recursive functions.

They also talked about decreased performance, but JSC team seems to have implemented it with a minimal performance impact, so it's definitely possible. More importantly, JS is a language to make development easy -- we have wasm in development for when speed becomes an issue and it's faster anyway (the label proposal would have also solved the issue by giving devs a choice).

They then dropped their own proposal, but still simply refused to re-add the feature as per the spec in a bid to force a spec change. I have some understanding of not turning a feature on until a newer proposal is considered, but when they dropped that proposal and still refused to implement, they lost all credibility in my estimation.

https://webkit.org/blog/6240/ecmascript-6-proper-tail-calls-...

https://www.more-magic.net/posts/internals-gc.html

<soap-boxing intensifies>

I think this is indicative of a general "we know better than you" attitude on the part of the engine designers (especially the ones that sit on the spec committee). Another amazing example of this is the private variable proposal. NEVER has a JS proposal received such outspoken disapproval. Complaints range from the mostly insignificant "it's ugly" to the important "it goes against JS's core prototypical nature" to the very important "refactoring and maintaining this will be a nightmare due to dedicated syntax and JS being so dynamic", "it looks like a normal JS property that is hidden, but it in fact has almost nothing in common with normal properties even though the access syntax looks similar" or "this screws with inheritance, subclassing, destructuring syntax, etc".

Instead of listening, they locked down all dissenting discussions on the proposal's github and are going full steam ahead. EDIT: some discussions are unlocked (see below for a decent argument summary).

https://github.com/tc39/proposal-class-fields/issues/150

Decorators, pipe operators, tuples, immutable records, and slice notation are all well-understood ideas. Most don't require huge amounts of work to implement, but result in large quality-of-life improvements for developers. Most importantly, they aren't really controversial at all and most are easily transpilable so devs could start using them today (unlike private vars which either don't actually transpile correctly or create a mountain of unreadable garbage)

Why can't we work on these t...

I would also like proper tail call optimization but it's very rare I need to do long loops in CPS style without a call in there that will reset the stack. And in any case, you don't need to call nextTick every iteration. Javascript stacks are over 10000 deep nowadays. If you need performance, call nextTick every 1000 iteration and you will be fine. Note that promises force the stack to be reset all the time and you have no choice to take the performance hit.
Presumably the "// set state to finished" involves a return statement, otherwise "check()" will continue to be called and ultimately the state will be set back to not done. This applies to both the async version and the chain() version. Personally I find it confusing that the comment hides a control flow statement when the text of the comment suggests that it's just setting a state variable.

Surely a better comparison for the async function would be a chain() that manually recreates the loop using a parameter or local captured variable, like shown below. They're still complex but if anything show the complexity more clearly, especially if 6 is not hardcoded but could be passed as a parameter. I suppose having the unrolled version from the article plus one of these would be best of all, to show the problem from all angles.

    function chainWithParam() {
        var checkAndIterate;
        checkAndIterate = (result, i) => {
            if (result) {
                // set state to finished
                return;
            }
            if (i == 6) {
                // set state to not done
                return;
            }
            check()
                .then(result => checkAndIterate(result, i + 1))
                .catch(error => /* set state to failed */);
        }
        checkAndIterate(false, 0);
    }
    
    function chainWithCapturedLocal() {
        i = 0;
        var checkAndIterate;
        checkAndIterate = result => {
            if (result) {
                // set state to finished
                return;
            }
            if (i == 6) {
                // set state to not done
                return;
            }
            ++i;
            check()
                .then(checkAndIterate)
                .catch(error => /* set state to failed */);
        }
        checkAndIterate(false);
    }
I'm not a JavaScript programmer so there could be mistakes in the above code. I'd be interested to know if there is, especially whether it was really necessary to declare the checkAndIterate variable on a separate line to its assignment.

Surely the "manual" function could be implemented with some sort of loop-like

I think most JS programmers do something along the lines of...

    function check() {
        return new Promise(async (resolve, reject) => {
            for(let i = 0; i < 6; ++i) {
                if(await doCheck()) {
                    resolve(true);
                }
            }
            reject();
        });
    }
or...

    async function check() {
        for(let i = 0; i < 6; ++i) {
            const status = await doCheck();
            if(status) {
                return status;
            }
        }
        throw new Exception('failed the 6 checks');
    }
Though, the same thing could certainly be accomplished with pure promise chains. One could also use the setTimeout method in situations where elapsed time is more important than a fixed number of calls.
The article would have been better without the last example. It doesn't clarify much and it's not something that anyone should ever write.
It serves as a nice reminder that having a blog does not make a person an authority.
I know its kind of meta, but what's with all the blog posts lately hosted on a *.christmas domain? These posts seem to have nothing to do with the holiday

I'll admit the only reason I've noticed the domain is my corporate IT seems to have all sites on weird TLDs blocked by default :/

Apparently it's supposed to be some kind of advent calendar type thing, 25 posts in 25 days. Which explains the christmas tld.
Exactly that. Bekk, a Norwegian agency, has built a few advent calendars themed around various aspects of programming and product development. There’s FP, JS, Kotlin, UX, etc
(comment deleted)
(comment deleted)

  check()
    .then(result => {
      if (result) {
         // set state to finished
      }
      check()
        .then(result => {
          if (result) {
              // set state to finished
          }
          check()
            .then(result => {
              if (result) {
                 // set state to finished
              }
              check()
                .then(result => {
                  if (result) {
                    // set state to finished
                  }
                  check()
                    .then(result => {
                      if (result) {
                         // set state to finished
                      }

                      // set state to not done
                    })
                    .catch(error =>  // set state to failed);
                })
                .catch(error =>  // set state to failed);
            })
            .catch(error =>  // set state to failed);
        })
        .catch(error =>  // set state to failed);
    })
    .catch(error =>  // set state to failed);

For the love of everything that’s sacred, please don’t do this. The strength of promises is that they can free us from that exact kind of callback hell, and they do that by being chainable.

  const resultOrNull = await Promise.resolve(null).
       then(result => result || check()).
       then(result => result || check()).
       then(result => result || check()).
       then(result => result || check()).
       then(result => result || check()).
       catch(error => null);
Calling .then/.catch inside of a .then/.catch is a huge red flag. Almost always, you want to return the promise and chain instead.
Came here to comment on that example as well. I started panicking that someone might actually do that.
You’re right of course.

But you having the “.” dot one line above made my spine tingle in all the wrong ways. Not that it matters.

The dot one line above is the better style. Change my mind.

Pasting

  check()
    .then()
in node REPL gives :

  > check()
  undefined
  > .then()
  Invalid REPL keyword

Pasting

  check().
    then()
Gives the expected result.

More generally, Javascript rules about whether a newline constitutes the end of a statement or not are pretty confusing (at least for me), so I prefer using a form that makes it explicit when a statement continues on the next line.

I see your perspective but a function call on a line with just the two spaces looks like poor formatting. At a glance it doesn't appear connected to the previous line until I look at the end of that previous line.

My statements end in ; Lines that begin with something other than const or let are likely function calls. If it starts with . then it is connected with the previous line and the statement isn't complete.

But if you're consistent in that style that is all important. Better consistent style in a project than one style over another.

I go back and forth on this, I can't make up my own mind let alone change anyone else's!

In any case, I used to do what you do – for pretty much the same reasons – but then I've gone back to starting lines with dot (and ?, ||, &&, and other usual suspects) because I parse code left-to-right, so the presence of such a character tells me that I need to look at the preceding line(s) to get the full picture, and it doesn't so much matter if it makes things paste-friendlier. Without the dot, I've found myself move things around that shouldn't be moved around for example:

    check().
      // Insert stuff here and things break, the lack of dot on the following line had me fooled
      then()
I have no good answers for this, and like I said I go back and forth on this – sometimes in the same file even! I think generally I don't particularly like splitting a statement over multiple line's, but at the same time I like too keep a hard limit of the number of characters I can put on each line. First world programming problem for sure.
> a form that makes it explicit when a statement continues on the next line

I prefer to know that a line is dependent on the previous by having all the dots, pipes, etc on a new line. So I skim the first few characters of every line to see what's happening.

An argument not mentioned here is that having the operator on the new line results in cleaner diffs, as only one line changed. However, I don't care for that and am scared of trailing commas too.

This is because of tutorials like this that it took me so long to understand promises .