发送响应后继续执行(Cloud Functions for Firebase)

28
我正在使用带有https触发器的Firebase函数,想知道在向客户端发送响应后,函数会继续执行多久。我想先向客户端发送响应,然后执行另一个操作(发送电子邮件)。目前,我的做法如下:

我正在使用带有https触发器的Firebase函数,想知道在向客户端发送响应后,函数会继续执行多久。我想先向客户端发送响应,然后执行另一个操作(发送电子邮件)。

module.exports.doSomeJob = functions.https.onRequest((req, res) => {
    doSomeAsyncJob()
    .then(() => {
        res.send("Ok");
    })
    .then(() => {
        emailSender.sendEmail();
    })
    .catch(...);
});

上述代码对我有效,但我怀疑这个代码只是有效,因为在res.send完成之前已经发送了邮件,所以我想知道终止进程的过程是如何工作的,以确保代码不会出错。

3个回答

37

你应该预期 HTTP 函数在发送响应后立即终止。任何其他行为都是一些运气或竞态条件的组合。不要编写依赖于运气的代码。

如果您需要在工作完全完成之前向客户端发送响应,则需要启动第二个函数来继续 HTTP 函数停止时的工作。通常使用发布 / 订阅函数来处理此类需求。让 HTTP 函数向另一个函数发送发布 / 订阅消息,然后仅在消息发送后终止 HTTP 函数(通过发送响应)。


1
嘿,这似乎是唯一的方法。但如果我查看发布/订阅的文档,我似乎只能找到订阅方面的信息,从云函数中如何调用它并没有找到。您是否可以扩展您的答案,包括实际如何做到这一点? - Daan Luttik
2
您可以使用谷歌云 pubsub 客户端发送消息。我个人没有试过。https://cloud.google.com/nodejs/docs/reference/pubsub/0.16.x/ - Doug Stevenson
@DougStevenson - 你能否添加一个例子? - Unnikrishnan
3
这里提供了一份 Google 文档的参考,明确说明了“在发送 HTTP 响应后执行的代码可能会随时被中断”。 https://cloud.google.com/functions/docs/concepts/exec#function_execution_timeline - Timothy Johns
1
@Unnikrishnan 这里有一个不错的例子:https://dev59.com/D1IH5IYBdhLWcg3wGY8n#61525321 - Lauren ten Hoor
1
这个解决方案的问题是发布/订阅可能需要2分钟才能触发 - 在生产中看到了这一点。 - nbransby

11
如果期望的响应与执行结果无关,则可以使用
module.exports.doSomeJob = functions.https.onRequest((req, res) => {
res.write('SUCCESS')
return doSomeAsyncJob()
.then(() => {
    emailSender.sendEmail();
})
.then(() => {
    res.end();
})
.catch(...);
});

当收到请求时,这将立即发送响应,但保持函数运行直到调用res.end()

客户端可以在收到响应后结束连接,但云函数将继续在后台运行

不确定这是否有帮助,但考虑到执行发布/订阅需要额外的处理并需要时间来执行,这可能是客户端需要在非常有限的时间内获得响应的解决方法。


1
太好了,谢谢!我的应用场景是 Slack,在 300 毫秒内如果没有收到 200 状态响应就会报错。这个解决方案似乎修复了我遇到的问题。 - Andrew Schwartz
我很高兴它有所帮助。 - Kisinga
3
这种方法并不总是有效的,往往失败的概率更大。Cloud Functions在向客户端发送响应后会限制计算资源,并且不能保证任何未完成的异步工作都会完成。 - Doug Stevenson
2
这种假设在实践中是不成立的。函数在响应发送后立即终止。不存在所谓的“部分响应”。响应被缓存在内存中,并一次性完整地发送。你可能有几分之一秒的时间去计算其他东西,但不能依赖于它。我强烈建议你自己尝试一下,看看它并不真正起作用。 - Doug Stevenson
只要云函数调用是幂等的,我认为您可以让服务重试。 大多数重试发生在短时间内,第二次调用是“热的”,执行速度更快。 因此,响应可以更快地给出。 由于幂等性,执行结果不会导致错误或重复结果。 这是一种hack,但可能比创建代理更好,后者虽然是更好的解决方案,但背离了首先拥有云函数的目的。 - Kisinga
显示剩余8条评论

5

TL;DR

在调用res.send()之后,https函数将很快终止,但不能保证不会执行res.send()之后的任何代码。

详细回答包含以下两个方面:

  1. 正如Doug所指出的那样,在res.send()之后不要放置你希望被执行的任何其他代码。
  2. https函数将很快终止,但不要期望确切地不执行res.send()之后的0行代码。

我遇到过这样一种情况:对于一个数据库维护脚本,如果没有记录符合我的条件,我使用res.send()来结束请求,并在其后添加了其他逻辑。我原本以为该部分代码不会被执行,因为我已经终止了请求。

下面是一个产生意外结果的示例:

exports.someFunction = functions.https.onRequest((req, res) => {
  if (exitCriteria === true) {
    // we can exit the function, nothing to do
    console.log('Exit criteria met')
    res.status(200).send()
  }

  // code to handle if someCriteria was falsy
  console.log('Exit criteria not met, continue executing code')
})

在上面的示例中,我期望res.send()立即终止函数 - 但事实并非如此,第二个console.log可能也会被触发 - 及您可能拥有的任何其他代码。然而,这并不是保证的,因此在某些时候执行可能会突然停止。
产生正确结果的示例:
exports.someFunction = functions.https.onRequest((req, res) => {
  if (exitCriteria === true) {
    // we can exit the function, nothing to do
    console.log('Exit criteria met')
    res.status(200).send()
  }
  else {
    // code to handle if someCriteria was falsy
    console.log('Exit criteria not met, continue executing code')
  }
})

在这个版本中,你将只会看到一个console.log的输出 - 这正是我最初想要的。

你知道我们是否可以不使用 else,而直接使用 return res.send() 吗? - picklepick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接