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:
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.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
getFieldsFromRequest()
and extractCourseIdFromEmailAddress()
– no issues here.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.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 await
ing the function getEmailOfCourseWithCourseId()
above, these functions (and the if operator) will wait until that has happened (aka promise has resolved), then run.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.// 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
☞ The Complete JavaScript Course 2019: Build Real Projects!
☞ Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)
☞ JavaScript Bootcamp - Build Real World Applications
☞ 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