在我完成 electrade【https://www.electrade.app/】 的工作之余,还帮助一个朋友的团队完成了他们的项目。最近,我们希望为这个项目构建一个 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个异步函数和主函数来捕获错误。此外,数据库代码不能原封不动的复制 —— 它仅用于说明目的!
原文:https://nikodunk.com/how-to-chain-functions-with-await-async/