How to chain functions with await/async

Originally published at https://nikodunk.com

Next to my main job of building out and running electrade I help a friend’s team with their project. Recently, we wanted to build a Craiglist-style anonymous email relay with a “serverless” Google Firebase Function (same as AWS Lambda, Azure Functions, etc) for this project. So far I’ve found handling async operations with .then() callbacks easier to think through, but wanted to use async/await here because it’s so much cleaner to read. I found most articles out there on chaining multiple functions unhelpful as they tend to post incomplete demo code that’s copy/pasted from MSDN. There are a few hard-to-debug pitfalls on async/await, and since I fell into all of them I’ll post my complete code here and explain my learnings so you don’t have to.

Here’s working code that chains multiple functions, waits for everything to resolve, and then sends the result. Main mistakes were:

  1. Every async function myFunction(){ <your code here> } declaration automatically wraps the whole async function’s code (ie. <your code here>) in a new Promise, and turns any return x within the code into resolve(x). But you still need to await outside of it (ie. let y = await myFunction()) or it won’t actually wait. The debugging around this is super annoying.
  2. Side note – in cloud functions, you must send a response with res.send(), or the function will assume it’s failed and re-run it. This obviously must happen once everything has run or your promises will get cancelled.

The code below does

  • We have 2 normal sync functions getFieldsFromRequest() and extractCourseIdFromEmailAddress() – no issues here.
  • Then we have async function getEmailOfCourseWithCourseId() which gets the course’s email address from Firestore. We don’t know how long getting stuff from Firestore will take so it’s async, and will return (or resolve in promise parlance) the courseEmail we need to run the next 2 functions.
  • The next 2 functions, saveToCloudFirestore() and sendEmailInSendgrid(), must not be run before getEmailOfCourseWithCourseId() is run and has returned courseEmail, or they will run with courseEmail as undefined and everything goes to shit. By passing in courseEmail that is awaiting the function getEmailOfCourseWithCourseId() above, these functions (and the if operator) will wait until that has happened (aka promise has resolved), then run.
  • Finally, res.send() must not be sent until saveToCloudFirestore() and sendEmailInSendgrid() have been run and returned their values, otherwise our whole cloud function will interrupt before the work is done. For this, we save the saveToCloudFireStore() and sendEmailInSendgrid() responses (the stuff they return) into a variable whose sole purpose is to mark when the above function as done. This in a sense replaces a .then(): it waits till both of these variables (savedToCloud and sentEmail) “have arrived” (aka their Promise has been resolved), then runs res.send() with them.
  • For readability’s sake, I have removed the try/catch wrappings here that you should be doing in practice. You should never not catch errors, but removing them makes the async/await concept easier to understand.
// this is the cloud function you can call over HTTP. It is basically for email relay:

// it gets an email from sendgrid, parses the fields, looks up the real email with the courseId,

// saves to FireStore and sends and email with sendgrid.

// Finally, it sends a res.send() to end the cloud function

 

// {* import a bunch of shit *}

 

// main function

exports.emailFunction = functions.https.onRequest(async (req, res) => {

  let fields = getFieldsFromRequest(req); // sync

  let courseId = extractCourseIdFromEmailAddress(fields); // sync

  let courseEmail = await getEmailOfCourseWithCourseId(courseId); // async

  let savedToCloud = await saveToCloudFirestore(fields, courseEmail, courseId); // async

  let sentEmail = await sendEmailWithSendgrid(fields, courseEmail);  // async

  res.status(200).send(savedToCloud, sentEmail); // Once sentEmail and saveToCloud have been returned (aka promises have been resolved, aka their functions have been run), res.send() will run so Firebase/SendGrid know that func worked.

});

 

// Helper functions below

function getFieldsFromRequest(req) { // sync

    let fields = readTheFieldsFromReqWithBusboy(req)

    return fields;

}

 

function extractCourseIdFromEmailAddress(fields) { // sync

    let courseId = fields.to.substring(0, fields.to.indexOf(‘@’));

    return courseId;

}

 

async function getEmailOfCourseWithCourseId(courseId) { // async important

    let courseData = await database.get(courseId)

    let courseEmail = courseData.email;

    return courseEmail; // due to function being labeled async above, this is the equivalent of wrapping the whole function in ‘return new Promise(resolve) => {}’ and then returning a ‘resolve(result)’

}

 

async function sendEmailWithSendgrid(fields, courseEmail) { // async important

    let msg = {to: courseEmail, from: fields.from, text: fields.text}

    let sentEmail = await sendgrid.send(msg)

    return sentEmail; // due to function being labeled async above, this is the equivalent of wrapping the whole function in ‘return new Promise(resolve) => {}’ and then returning a ‘resolve(result)’

}

 

async function saveToCloudFirestore(fields, courseEmail, courseId) { // async important

    let savedToCloud = await database.add(fields, courseEmail, courseId)

    return savedToCloud;

}

Again, wrap the 3 last async functions and the main function in try{}catch{} to catch errors. Also, the database code cannot be copied over like this – it’s just for illustrative purposes. You have been warned!

Thanks for reading

If you liked this post, please do share/like it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading about JavaScript

The Complete JavaScript Course 2019: Build Real Projects!

Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

JavaScript Bootcamp - Build Real World Applications

The Web Developer Bootcamp

JavaScript Programming Tutorial - Full JavaScript Course for Beginners

New ES2019 Features Every JavaScript Developer Should Know

Best JavaScript Frameworks, Libraries and Tools to Use in 2019

React vs Angular vs Vue.js by Example

Microfrontends — Connecting JavaScript frameworks together (React, Angular, Vue etc)

Creating Web Animations with Anime.js

Ember.js vs Vue.js - Which is JavaScript Framework Works Better for You

Do we still need JavaScript frameworks?

#javascript #function

How to chain functions with await/async
1 Likes15.55 GEEK