
A brief look at async-await - sveingjoby
https://javascript.christmas/2019/9
======
sloonz

      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.

~~~
gherkinnn
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.

~~~
sloonz
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.

~~~
sosuke
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.

------
cyrusmg
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.

~~~
sveingjoby
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

~~~
cyrusmg
> "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.

~~~
sveingjoby
Update the article based on your feedback.

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

------
laurent123456
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.

~~~
mywittyname
It serves as a nice reminder that having a blog does not make a person an
authority.

------
quietbritishjim
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

~~~
mywittyname
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.

------
gbuk2013
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....](https://gir.me.uk/posts/node-8-async-await-performance-test.html)

~~~
koolba
> 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...
        }

~~~
gbuk2013
> 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.

~~~
Klathmon
>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.

~~~
koolba
I was about to write up that _exact_ example of the nested handlers.

~~~
Klathmon
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!

------
TheSoftwareGuy
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 :/

~~~
nkrisc
Apparently it's supposed to be some kind of advent calendar type thing, 25
posts in 25 days. Which explains the christmas tld.

~~~
gherkinnn
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

