在 Firebase Cloud Functions 中使用 async/await 时返回一个 Promise
Posted
技术标签:
【中文标题】在 Firebase Cloud Functions 中使用 async/await 时返回一个 Promise【英文标题】:Returning a promise when using async/await in Firebase Cloud Functions 【发布时间】:2019-01-27 02:32:33 【问题描述】:自从 Firebase Cloud Functions 支持节点 8 以来,我一直很高兴地使用 async/await。不过,我正在为一件事而苦苦挣扎。使用可调用函数时,被告知必须在函数中返回一个promise,否则将无法正常工作。使用原始承诺时,我很清楚如何使用它:
exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) =>
return promiseMethod().then((result) =>
return nextPromise(result);
).then((result) =>
return result;
).catch((err) =>
// handle err
)
);
但是现在,有了异步等待,我不确定如何返回这个“承诺链”:
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) =>
const res1 = await promiseMethod();
const res2 = await nextPromise(res1);
return res2;
// ??? Where to return the promise?
);
有人知道吗?
【问题讨论】:
“我不知道如何返回这个“承诺链”:”——正如你在你编写的代码中所做的那样。 好的,但是 firebase 控制台上的日志告诉我,我调用了该函数 2 次,而实际上我只调用了 1 次。我认为这与以错误的方式返回这些承诺有关。 Drop the pointless.then((result) => return result; )
您可以轻松设置示例项目并验证答案的声明。只需创建一个等待 10 秒然后返回结果的函数。如果你用 await 调用这个函数,firebase 函数会正确等待 10 秒然后返回结果。
我已将 google-firebase 团队的权威代码示例添加到我的答案中。如果这还不足以支持它,我不知道。
【参考方案1】:
HTTP 函数不返回承诺。他们只是发送一个结果。您仍然必须正确使用 Promise 才能发送结果,但不需要返回值。 HTTP 函数在响应发送时终止。见documentation for more details:
使用 res.redirect()、res.send() 或 res.end() 终止 HTTP 函数。
【讨论】:
.onCall-functions 也是这样吗?当我调用我的函数一次时,firebase 控制面板上的记录器说我的函数被调用了两次。我认为这与兑现这些承诺有关。 不,可调用函数要求您返回一个用您要发送给客户端的数据解析的承诺。这应该在文档中很清楚。 没错,所以我最初的问题适用于可调用函数:使用 async/await 时如何返回此承诺 好吧,您编辑了问题以显示一个可调用函数,而不是您最初拥有的 HTTP 函数。您实际上被问到了一个不同的问题,而不是第一个问题,这并不是 Stack Overflow 的工作方式。如果你完全改变问题的条款,你会迷惑所有人。 HTTPS 和 callable 在将数据发送回客户端的方式上非常不同。请花一些时间研究文档和示例以澄清这一点。【参考方案2】:如果需要,只需转换为 Promise。
即如果 nextPromise 返回一个 Promise:
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) =>
const res1 = await promiseMethod();
return nextPromise(res1);
);
另一方面,如果 nextPromise 是异步函数,只需将其转换为 Promise:
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) =>
const res1 = await promiseMethod();
return Promise.resolve(nextPromise(res1));
);
你也可以转换结果:
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) =>
const res1 = await promiseMethod();
const res2 = await nextPromise(res1);
return Promise.resolve(res2);
);
【讨论】:
这完全是不必要的:异步函数将在第一个“等待”时向调用者返回一个 Promise,最终将解析为您在 return 语句中返回的任何内容。将这个 Promise 包装在另一个 Promise 中没有任何效果。 我举了 3 个例子。第一个是最佳的,其他两个不是,并且只是为了澄清有不止一种方法可以做同样的事情。 Promise.resolve 完全没有必要且具有误导性。您可以在这两种情况下将其删除,没有任何缺点。这只是不必要的代码,会分散异步函数的工作方式【参考方案3】:"await" 只是返回 Promise 的语法糖
当您编写异步函数时,代码实际上会退出该函数并在遇到第一个 await 时返回一个 Promise。 await 之后的所有代码都将转换为 then()。
因此,对于 firebase 而言,使用 async/await 编写代码非常省事,而且根据我的经验,更不容易出错,因为我可以更轻松地在我的代码中构建 try&catch!
证明:
只需在控制台中运行它:
async function iAmAsync()
await new Promise(r => window.setTimeout(r, 1000))
return 'result'
let x = iAmAsync()
console.log(x)
将打印:Promise<resolved>: "result"
TL;DR:您无需更改任何内容 - 如果您编写具有多个等待的代码,这将由 firebase 处理,就像一系列承诺一样,一切都会正常工作。
由于我的回答被否决,这里是 google firebase 团队本身的权威代码示例:
https://github.com/firebase/functions-samples/blob/master/quickstarts/uppercase/functions/index.js
exports.addMessage = functions.https.onRequest(async (req, res) =>
// [END addMessageTrigger]
// Grab the text parameter.
const original = req.query.text;
// [START adminSdkPush]
// Push the new message into the Realtime Database using the Firebase Admin SDK.
const snapshot = await admin.database().ref('/messages').push(original: original);
// Redirect with 303 SEE OTHER to the URL of the pushed object in the Firebase console.
res.redirect(303, snapshot.ref.toString());
// [END adminSdkPush]
);
【讨论】:
【参考方案4】:看来,我们必须像这样等待几个 Promise:
const listOfAsyncJobs = [];
listOfAsyncJobs.push(createThumbnail(1, ...));
listOfAsyncJobs.push(createThumbnail(2, ...));
listOfAsyncJobs.push(createThumbnail(3, ...));
...
return Promise.all(listOfAsyncJobs); // This will ensure we wait for the end of the three aync tasks above.
【讨论】:
【参考方案5】:无论你返回什么异步方法,它都会自动包装在 Promise 中。 例如
const myFun = async () => return 5
myFun();
// Output in the console
Promise <fulfilled>: 5
你可以链接返回的结果,因为它是一个承诺
另一个答案中建议的增强示例
const myFun4 = async () =>
const myNum = await new Promise(r => window.setTimeout(() => r(5), 1000));
const myNum2 = await new Promise(r => window.setTimeout(() => r(5), 1000));
return myNum + myNum2;
myFun4().then((n) => console.log(n));
// Output
10
【讨论】:
【参考方案6】:async-await
函数的返回值为Promise
。
https://developer.mozilla.org/en-US/docs/Web/javascript/Reference/Statements/async_function#return_value
所以,您实际上所做的是返回一系列承诺。
const nextPromise = () =>
console.log('next promise!');
return new Promise((resolve, reject) =>
setTimeout(() =>
resolve('next promise result')
, 3000)
);
const promiseMethod = () =>
console.log('promise!');
return new Promise((resolve, reject) =>
setTimeout(() =>
resolve('promise result');
, 2000)
);
exports.createBankAccount = functions.https.onCall((data, context) =>
return promiseMethod().then((result) =>
return nextPromise(result);
).then((result) =>
return result;
).catch((err) =>
// handle err
console.log(err);
)
);
exports.createBankAccountAsync = functions.https.onCall(async (data, context) =>
const result = await promiseMethod();
const res = await nextPromise(result);
return res;
);
我在 firebase 上创建了测试项目,两个函数调用都给出了相同的日志。
【讨论】:
【参考方案7】:这种情况下的解决方案是Promise.all()
。
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) =>
const promises = [];
const res1 = await promiseMethod();
const res2 = await nextPromise(res1);
promises.push(res1);
promises.push(res2);
// Here's the return of the promises
return Promise.all(promises).catch(error => console.error(error));
);
你可以在freecodecamp.org/promise-all的这篇文章中找到更多关于 Promise 的信息
【讨论】:
【参考方案8】:您的示例代码成功了。
Async/await 只是一种更新的承诺方式。它们可以互换使用。
这是同一个函数的一个示例 promise 和 async/await。
这个
exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) =>
return promiseMethod().then((result) =>
return nextPromise(result);
).catch((err) =>
// handle error here
)
);
等价于:
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) =>
try
const result = await promiseMethod();
return nextPromise(result); // You are returning a promise here
catch(e)
// handle error here
);
请注意,在这两种情况下,您最后都会返回一个承诺。此 onCall 函数的返回值将是 nextPromise(result)
的任何值。由于您返回的是nextPromsie(result)
,因此您无需等待。
【讨论】:
【参考方案9】:要查看问题的代码解决方案,请查看dshukertjr 的答案。
如果您想了解如何使用 async/await 返回“承诺链”,以下是您的答案:
你不能! 为什么 ?因为 await 用于等待 Promise 完成。一旦 await 返回一个值,它们就不再是 Promise。
因此,如果您绝对想使用 await 返回一个 Promise,您可以等待返回 Promise 的两个函数之一,但不能同时等待。
这里有两种方法:
一个:
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) =>
try
const result = await promiseMethod();
return nextPromise(result); // You are returning a promise here
catch(e)
// handle error here
);
乙:
exports.createBankAccount = functions.region('europe-west1').https.onCall(async (data, context) =>
return promiseMethod().then(async (result) =>
return await nextPromise(result);
).catch((err) =>
// handle err
)
);
A 和 B 之间的唯一区别是 A 在返回 Promise 之前等待“PromiseMethod”完成。而 B 在被调用后立即返回一个 Promise。
【讨论】:
【参考方案10】:由于您需要返回 Promise,因此您可以创建 Promise 对象并在处理完所有 Promise 后从 api 解析/拒绝(返回)您的响应。
选项 1:
exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) =>
return new Promise(async (resolve, reject) =>
try
const res1 = await promiseMethod();
const res2 = await nextPromise(res1);
// This will return response from api
resolve(res2);
catch (err)
// Handle error here
// This will return error from api
reject(err)
)
);
选项 2:
exports.createBankAccount = functions.region('europe-west1').https.onCall((data, context) =>
return new Promise(async (resolve, reject) =>
const res1 = await promiseMethod();
const res2 = await nextPromise(res1);
// This will return response from api
resolve(res2);
)
.then((val) => val)
.catch((err) =>
// Handle error here
// This will return error from api
return err
)
);
【讨论】:
遗憾的是,promise executor 函数不应该是异步的。以上是关于在 Firebase Cloud Functions 中使用 async/await 时返回一个 Promise的主要内容,如果未能解决你的问题,请参考以下文章
在 TypeScript 中为 Firebase Cloud Function 配置 mailgun
在 Firebase Cloud Function 中发出 Youtube Data API 请求
从请求中获取用户信息到 Firebase 中的 Cloud Function
Firebase:Firestore 未在 Cloud Function 的计划函数中更新数据