【问题标题】:Firebase 'Unhandled Rejection' and 'Can't set headers after they are set' JavaScript errorFirebase“未处理的拒绝”和“设置标头后无法设置标头”JavaScript 错误
【发布时间】:2020-07-12 17:40:06
【问题描述】:

首先,请注意,我对 JS 和编码非常陌生 :)

期望的行为: 我编写了以下 JS HTTPS Firebase 函数,该函数接受查询参数locationId,它执行 GET API 调用并将响应保存回 Firebase。该代码根据需要正确地将数据保存到 Firebase。我遇到过类似的问题,但我正在努力使这些解决方案适应我下面的具体问题。据我所知,我只发送一次响应。

具体错误:以下是控制台输出

标头发送到客户端后无法设置

未处理的拒绝

我的功能:

exports.doshiiGetMenuForOnboardedVenue = functions.https.onRequest((req, res) => {

  // Forbidding PUT requests.
  if (req.method === 'PUT') {
    return res.status(403).send('Forbidden!');
  }

  cors(req, res, () => {

    const locationId = req.query.locationId;

    console.log('locationId', locationId);

    if (locationId){

      console.log('locationId', locationId);

      var token = jwttoken();

      const options = {
          headers: {
            'content-type': 'application/json',
            'authorization': 'Bearer ' + token}
        };

        const uri = 'https://sandbox.doshii.co/partner/v3/locations/' + locationId + '/menu?lastVersion=:lastVersion&filtered=true'

        axios.get(uri, options)
          .then(response => {

             return admin.database().ref(`/venue-menus/${locationId}/`).set(response.data)
             .then(response => {
               return res.status(200).send(locationId)
             })
             .catch(err => {
               return res.status(500).send({error: err})
             })

          })
          .then(response => {
                    return res.status(200).send(locationId)
            })
          .catch(err => {
            return res.status(500).send({error: err})
          })//end axios

    } else {

      return res.status(500).send({error: 'locationId missing'})

    }//end if-else (!locationId)

  })//end cors

});

【问题讨论】:

  • 错误消息告诉您,您的代码试图发送多个响应。每次函数调用只能调用一次res.send

标签: javascript firebase error-handling promise google-cloud-functions


【解决方案1】:

通过展平嵌套的 Promise,您可以看到您的代码正在执行以下指令(当 axios 调用未引发错误时):

admin.database().ref(`/venue-menus/${locationId}/`).set(response.data))
  .then(response => res.status(200).send(locationId))
  .catch(err => res.status(500).send({error: err})
  .then(response => res.status(200).send(locationId)) // this line is always called after either of the above.
  .catch(err => res.status(500).send({error: err})

通常情况下,除非需要,否则不应将 promise 嵌套在它们自己的 then()catch() 处理程序中,因为这会导致像这样的奇怪效果。

此外,如果您的代码需要使用 //end axios//end cors 消息,您应该扁平化您的代码,这样在没有这些消息的情况下才有意义。

调整您的代码以“快速失败”、更正您的 API 响应并适当隐藏错误堆栈跟踪可以:

const cors = require('cors')({
  origin: true,
  methods: ["GET"]
});


exports.doshiiGetMenuForOnboardedVenue = functions.https.onRequest((req, res) => {
  cors(req, res, (err) => { // note: cors will handle OPTIONS method

    if (err) {
      // note: log full error at ERROR message level
      console.error('Internal CORS error:', err);
      // note: return only generic status message to client
      return res.status(500).json({error: 'Internal Server Error'});
    }

    // Forbidding anything but GET requests.
    if (req.method !== 'GET') {
      // 405 METHOD_NOT_ALLOWED
      return res.status(405)
        .set('Allow', 'GET')
        .json({error: 'Not Allowed!'});
    }

    const locationId = req.query.locationId;

    console.log('locationId', locationId);

    if (!locationId) {
      // 400 BAD_REQUEST
      return res.status(400).json({error: 'locationId missing'})
    }

    var token = jwttoken();

    const options = {
        headers: {
          'content-type': 'application/json',
          'authorization': 'Bearer ' + token
        }
      };

    // note: Don't forget to enable billing for third-party APIs!
    const uri = 'https://sandbox.doshii.co/partner/v3/locations/' + locationId + '/menu?lastVersion=:lastVersion&filtered=true'

    axios.get(uri, options)
      .then(response => admin.database().ref(`/venue-menus/${locationId}/`).set(response.data))
      .then(() => {
        // note: as locationId was already sent by the client, send new/useful
        // information back or nothing but the right status code
        res.status(200).json({ ref: `/venue-menus/${locationId}/` });
      })
      .catch(err => {
        // note: log full error at ERROR message level
        console.error('Failed to retrieve/save API data:', err);
        // note: return only message to client
        res.status(500).json({error: err.message || 'Internal Server Error'});
      });
  });
});

【讨论】:

  • 我在最后一行}); 收到了parsing error: Unexpected token ","
  • @Roggie 道歉,最后错过了括号 )
  • 感谢@samthecodingman,但现在我明白了:Each then() should return a value or throw
  • @Roggie 这是 linter 的编程风格警告之一,用于防止 Promise 链中出现意外的 undefined 值,而不是上面代码的实际错误。您可以在 linter 的配置中禁用警告,也可以在每个 then()catch() 处理程序的末尾添加显式 return; 语句。
  • 如果您是初学者,我也推荐Firebase Slack,因为有大量讨论导致解决方案,因此您可以了解其他用户如何调试他们的问题。您可能还会发现 my other answers 很有帮助。另外,如果答案有用,也不要忘记对答案进行投票 - 接受的答案和投票将分别跟踪。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-02
  • 2023-04-04
  • 2017-09-08
  • 1970-01-01
  • 1970-01-01
  • 2017-12-20
相关资源
最近更新 更多