
How to chain multiple functions in JavaScript properly with async/await - nikodunk
https://nikodunk.com/how-to-chain-functions-with-await-async/
======
thethirdone
The comments about a function call waiting until an argument is defined is a
potentnially misleading way of thinking about await. The calls to
saveToCloudFirestore and sendEmailInSendgrid do not execute in parallel. await
only really replaces .then()s and can't (as far as I know) achieve an effect
like Promise.all()

I don't understand the line:

> return; // the return is only here because .then() callbacks need a return

I have never seen anyone do this before and have seen many .then() callbacks
work. As far as I can tell `return;` is equivalent to letting the function
reach the end.

Nitpick: in the sendEmailInSendgrid function, msg is never defined. I assume
res.msg or res was meant.

~~~
johnsonjo
> await only really replaces .then()s and can't (as far as I know) achieve an
> effect like Promise.all()

Although it's not completely equivalent to a Promise.all, you can actually use
vanilla await with basically the same effect as promise.all(). Like so: [0]

    
    
      const timeout = ms => new Promise(res => setTimeout(res, ms));
      
      (async () => {
        const startTime = Date.now();
        const first = timeout(1000);
        const second = timeout(500);
        const third = timeout(800);
        await first;
        await second;
        await third;
        console.log(`Function completed in ${Date.now() - startTime} ms`)
      })();
    

This should output the completion time as somewhere very near to 1000ms. The
reason they aren't completely equivalent is if one promise fails in
Promise.all they all immediately fail whereas this only throws once you await
the failed promise. Of course you could also use `await Promise.all([...])` in
async/await too. You could try this in your browser console (I got it to log
the output in chrome, but not safari for some reason) or you could go to link
[0] and try it on jsfiddle.

[0]:
[https://jsfiddle.net/johnsonjo4531/r5fasqdo/1/](https://jsfiddle.net/johnsonjo4531/r5fasqdo/1/)

~~~
vimslayer
Note that this won't work concurrently if the promises are some kind of "lazy"
promise implementation that executes the operation only when `.then` is
called. Like with Knex, for example. I prefer

    
    
        const [a, b] = await Promise.all([promiseA, promiseB])

------
WorldMaker
> Every async function needs a new Promise, and needs to resolve(). It won’t
> complain if you don’t do this, but it also won’t actually wait. The
> debugging around this is super annoying.

Async functions manage the "outer" promise automatically for you. It looks
like you are "double-wrapping" promises unnecessarily by creating manual
promises around a library that already looks to be returning Promises (or at
least, is thenable and Promise-like).

So far as I can tell, it could be simplified to:

    
    
        async function getEmailOfCourseWithCourseId(courseId) { // async important
            try {
              const course = await doAsyncStuffWithFirestore(courseId)
              return course.email
            } catch (error) {
              console.error(error)
            }
        }
    
        async function sendEmailInSendgrid(fields, courseEmail) { // async important
          try {
            const msg = {to: courseEmail, from: fields.from, text: fields.text}
            await doAsyncStuffWithSendGrid(fields, courseEmail)
            return msg
          } catch (error) {
            console.error(error)
          }
        }
    
        async function saveToCloudFirestore(fields, courseEmail, courseId) { // async important
          try {
              return await doAsyncStuffWithFirestore(fields, courseEmail, courseId)
          } catch (error) {
            console.error(error))
          }
        }
    

The thing that stands out refactoring it to use awaits is that your try {}
catch {} may be too low and you should move the try {} catch {} up higher in
your call stack. (Do you really want to ignore the error and continue with all
of these inner async functions? Because that is what you are currently doing.)

~~~
nikodunk
This is an excellent comment. Thank you! I see what you mean – I am double-
wrapping with Promises and could replace this with try{} catch{}. My error was
that I previously simply wrote

    
    
       return course.email
    

or whatever, which as far as I could tell did not make the outer function
wait.

I will correct this in the article once I've tested it.

~~~
WorldMaker
Yeah, the trick to remember is the await in the line before (and in general,
anywhere you would .then() you can await, .catch() you can try/catch). You'll
notice you can even `return await`, and that may have been the particular
missing part you wanted. Where promises typically auto "flatten" their returns
(return a Promise<T> in .then() and the return of .then() is a Promise<T> not
a Promise<Promise<T>>), async/await does not flatten the promise automatically
in case you meant to do that.

If you are writing a lot of async/await code, you may want to take the time to
configure your linter for promises/async/await or try Typescript, as either or
both can be very useful tools at spotting cases where you missed an await
before a Promise.

------
koolba
> For readability’s sake, I have removed try/catch wrappings here that you
> should be doing in practice. You should never not catch errors, but makes
> the async/await concept way easier to understand.

On the contrary, you should never catch errors unless you’re actually going to
handle them. A try/catch silently ignoring failures means you don’t care about
the operation’s result. The default should be bubbling up errors to a
centralized error handler.

Async functions make this even more convenient as all errors now become
promise rejections.

------
gitgud
One of the biggest gains in moving to async/await is the fact you can have a
single try/catch for both synchronous and asynchronous code. This makes async
functions which implement some kind of transaction much easier to implement.

------
ramblerman
> savePromiseDone && emailPromiseDone ? res.send() : null // sync, will wait
> until emailPromiseDone and savePromiseDone are defined ie. their functions
> are done

I'm not sure this is correct. Getting to this line already implies they are
done, as the await will block. You could just put res.send() on this line no?

~~~
Lyrkan
Yep, the comment is wrong, this line won't wait for anything but rather check
that both calls resolved to a truthy value.

There are also some other incorrect things in that post that makes the code
overly complicated, like:

> Every async function needs a new Promise, and needs to resolve()

That's definitely _not_ needed unless you call something that runs
asynchronously without being `async` or returning a standard `Promise` (in
which case you could probably use a generic wrapper to convert them to
`Promise` objects and avoid having to do that everytime).

------
nikodunk
Hey gang!

I've always found it easier to think through async operations with .then(),
but recently decided to make the switch. Totally worth it for conciseness! I
thought I'd share my learnings above.

Suggestions welcome!

~~~
cprecioso
Hey, something is wrong with your interpreter or your transpiler if you need
to wrap every `async` function in a `return new Promise`. If you mark a
function as `async` it will do it automatically - in fact, if you look at e.g.
`nodent`'s output, it's exactly how it does it. Calling the `resolve` function
instead a `then` callback like that is a mess, and totally not needed. Also,
the `return` is not necessary, every function has an implicit `undefined`
return if not explicitly stated.

Moreover, in async functions you can take advantage of regular `try`/`catch`
when awaiting; it seems you're just swallowing errors in your functions, which
I guess is fine for the code you're showing. But keep in mind that a better
practice is to let those function throw and catch it with a try/catch at the
callsite.

Lastly, when calling the functions to save and send the email, you can use
`Promise.all` and parallelize them instead of running them serially, as they
don't seem to depend on each other. If you're meaning to send the email only
after it is saved, then you need to check the return value of the saving
before that; because neither of the calls will ever fail; just return
undefined.

~~~
nikodunk
Ah, excellent points. Thank you for the very carefully crafted comment. I will
update once I've re-tested with these changes.

------
nailer
Just a note that if you're using AWS Lambda, arc.codes has native async
function support, without callbacks. Ie, you just return a response instead of
running res()

[https://arc.codes/guides/http](https://arc.codes/guides/http)

And some cool middleware that's also await based:

[https://arc.codes/guides/middleware](https://arc.codes/guides/middleware)

------
lootsauce
I still find async.js to be a superior tool for complex use cases than
promises, async/await or callbacks. Think about it. A higher level abstraction
designed for specific use cases, as for example to do sequential or parallel
async operations is a far better approach imho than cobbling the same together
using generic async language feature of choice.

------
ArtRichards
Hi the sendgrid resolves variable msg but accepts the var res, is this
intentional? :)

~~~
nikodunk
Good point! Thank you! Added.

------
dang
A blog post is not a Show HN, so we've taken that out of the title. This is in
the rules:
[https://news.ycombinator.com/showhn.html](https://news.ycombinator.com/showhn.html).

~~~
nikodunk
thank you!

