How to chain multiple functions in JavaScript properly with async/await
nikodunk.com
nikodunk.com
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.
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. const [a, b] = await Promise.all([promiseA, promiseB])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.) 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.
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.
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.
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?
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).
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!
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.
let courseId = fields.to.substring(0, fields.to.indexOf('@'));
I've never seen this 'to' string method and I can't find a doc page for it. Can you explain it or point me towards a reference? Thanks
const fields = {
to: "foo@bar.com"
};
fields.to // "foo@bar.comAnd some cool middleware that's also await based: