在我完成 electrade
的工作之余,还帮助一个朋友的团队完成了他们的项目。最近,我们希望为这个项目构建一个 Craiglist 风格的匿名电子邮件中继,其中包含 “serverless” Google Firebase Function(与 AWS Lambda,Azure Function 等相同)。到目前为止,我发现用 .then()
回调处理异步操作更容易思考,但是我想在这里用 async/await,因为它读起来更清晰。我发现大多数关于链接多个函数的文章都没有用,因为他们倾向于发布从MSDN 复制粘贴的不完整的演示代码。在 async/await 上有一些难以调试的陷阱,因为我遇到了所有这些陷阱,所以我将在这里发布自己的完整代码并解释我的学习过程。
这是连接多个函数的工作代码,等待解决所有问题,然后 then 发送结果。主要错误是:
每个
async function myFunction(){ <your code here> }
声明自动将整个异步函数的代码(即<your code here>
)包装在new Promise
中,然后转换为return x
并在代码中加入resolve(x)
。 但是你还需要在它之外等待(即let y = await myFunction()
)或它实际上不会等待。这个调试是非常烦人的。在云函数中,你必须发送带有
res.send()
的响应,否则函数会认为它失败并重新运行它。
下面的代码要做这些事情:
我们有 2 个正常的同步函数
getFieldsFromRequest()
和extractCourseIdFromEmailAddress()
—— 这里没问题。然后我们需要
async
函数getEmailOfCourseWithCourseId()
从Firestore获取课程的电子邮件地址。我们不知道从 Firestore 获取内容需要多长时间,因此它是async
的,我们需要运行接下来的两个函数并返回(或以 promise 解析)courseEmail
。接下来的两个函数
saveToCloudFirestore()
和sendEmailInSendgrid()
,不能在getEmailOfCourseWithCourseId()
之前运行并返回courseEmail
,否则它们将认为courseEmail
未定义,这样的话一切都变得糟透了。 通过 awaiting 上面的函数 getEmailOfCourseWithCourseId() 并传递 courseEmail,这些函数(以及 if 运算符)将等到这种情况发生(也就是说已经解决),然后运再行。最后,在运行
saveToCloudFirestore()
和sendEmailInSendgrid()
并返回它们的值之前,不能发送res.send()
,否则我们的整个云函数将在工作完成之前中断。为此,我们将saveToCloudFireStore()
和sendEmailInSendgrid()
响应(它们返回的内容)保存到变量中,其唯一目的是标记上述函数何时完成。这在某种意义上取代了.then()
:它等待这两个变量(savedToCloud
和sentEmail
)“到达”(他们的 Promise 已经解决),然后运行res.send)()
。为了便于阅读,我已经删除了你应该在实践中进行的 try/catch 包装。你永远不应该捕获错误,但删除它们会使 async/await 概念更容易理解。
// 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;
}
最后用 try {}catch {}
包装最后3个异步函数和主函数来捕获错误。此外,数据库代码不能原封不动的复制 —— 它仅用于说明目的!