在 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 的计划函数中更新数据

Firebase Cloud Function在特定时间后自动删除数据[重复]

Firebase Cloud Function 在 .get() 完成之前结束