【问题标题】:Error handling using try / catch inside async / await on firebase onCall cloud function在firebase onCall云函数上使用异步/等待中的try/catch进行错误处理
【发布时间】:2019-03-12 17:17:58
【问题描述】:

我目前正在将此云功能部署到我的 firebase 应用程序中,并且我将使用 Node v8 运行时,因此我可以使用 async/await 语法。

我在处理同一函数中可能出现的不同类型的错误时遇到了一些问题。

完成后,该函数应接收 url 参数以向该 url 发出请求,从 response 正文中获取一些数据并保存它到数据库。此时,它只是返回它收到的用于测试目的的相同 url 字符串。

到目前为止,这就是我所拥有的:

const functions = require('firebase-functions');
const request = require('request');
const cheerio = require('cheerio');

exports.getDataFromUrl = functions.https.onCall((data) => {

  // PROMISIFIED REQUEST TO USE WITH ASYNC AWAIT
  const promisifiedRequest = function(options) {
    return new Promise((resolve,reject) => {
      request(options, (error, response, body) => {
        if (error) {
          return reject(error);
        }
        return resolve(response);
      });
    });
  };

  // CHECK IF URL IS PRESENT, IF NOT, THROW ERROR
  if (!data.url) {
    throw new functions.https.HttpsError('invalid-argument','The URL parameter was invalid.');
  }

  // URL passed from the client.
  const url = data.url;

    // IIFE ASYNC FUNCTION
    (async function() {

      // TRY BLOCK
      try {

        // REQUEST OPTIONS TO THE URL
        const urlOptions = {
          url: 'https://www.someINEXISTENT.url',
          method: 'GET',
          gzip: true,
          headers: {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.96 Safari/537.36'
          },
          jar: true
        };

        // CREATE RESPONSE AND CHEERIO OBJECT
        let response = null;
        let $ = null;

        // SEND REQUEST TO URL, AND PARSE WITH CHEERIO
        response = await promisifiedRequest(urlOptions);
        $ = cheerio.load(response.body);

      } // TRY BLOCK - END

      // CATCH BLOCK
      catch (error) {
        console.log('Caught an error: ' + error);
        throw new functions.https.HttpsError('unknown', error.message, error);
      }

      console.log('End of async function...');

    })()

  return {
    yourUrl : url
  };

});

当 URL 无效时发生的第一个错误情况运行良好。当我抛出以下错误时,执行停止:

throw new functions.https.HttpsError('invalid-argument','URL invalid.');

据我了解,需要抛出这个HttpsError 以便能够抓住客户。它有效。当它发生时,我在客户端收到此错误。

我的问题在于第二种类型的错误,它应该被我的 async 函数 中的 try/catch 语句捕获。例如,当我尝试请求不存在的 url 时,应该会发生该错误。

发生的事情是这样的(下图):

catch 块被激活,我可以在我的函数控制台上看到 `console.log(),但不知何故它不会抛出错误,并且它抱怨“在没有 catch 块的情况下抛出异步函数”,即使我是从一个 catch 块内扔它的。我的客户端代码根本没有收到此错误。从我的客户的角度来看,函数完成时没有任何错误。

错误:

错误:(节点:5160)UnhandledPromiseRejectionWarning:未处理的承诺 拒绝。此错误源于在异步内部抛出 没有 catch 块的函数,或者拒绝一个没有 用 .catch() 处理。 (拒绝编号:1)

我已经尝试添加和外部try/catch 块。并且还在 async IIFE 之后使用 .catch() 将错误抛出到 async 函数之外,但这并没有解决问题。

我做错了什么?

【问题讨论】:

  • 如果您要显示错误消息的文本,请将其复制到问题本身中,而不是添加屏幕截图。屏幕截图难以阅读,无法在 SO 上搜索。
  • 谢谢。我会编辑并添加它。

标签: javascript firebase async-await try-catch google-cloud-functions


【解决方案1】:

...但不知何故它不会抛出错误...

这是因为没有使用 async 函数创建的承诺,而 throw 拒绝了该承诺。 Promise 的规则之一是:您必须处理 Promise 拒绝或将结果返回给其他可以处理的东西。

functions.https.onCall 允许您使用return a promise,因此我将回调设为async 函数,而不是使用异步 IIFE。您无法同步提供结果或引发错误,因为您正在处理异步操作。

这些方面的东西(可能需要调整,请参阅*** cmets):

const functions = require('firebase-functions');
const request = require('request');
const cheerio = require('cheerio');

// *** No reason to define this within `onCall`'s handler
const promisifiedRequest = function(options) {
  return new Promise((resolve,reject) => {
    request(options, (error, response, body) => {
      if (error) {
        return reject(error);
      }
      return resolve(response);
    });
  });
};

exports.getDataFromUrl = functions.https.onCall(async (data) => { // *** Make it async, since `onCall` allows you to return a promise

  // CHECK IF URL IS PRESENT, IF NOT, THROW ERROR
  if (!data.url) {
    throw new functions.https.HttpsError('invalid-argument','The URL parameter was invalid.');
  }

  // URL passed from the client.
  const url = data.url;

  // *** No need for try/catch unless you want to change the error
  // *** If you do, though, add it back and use `throw` in the `catch` block (as you did originally).
  // *** That will make the promise this async function returns reject.

  // REQUEST OPTIONS TO THE URL
  const urlOptions = {
    url: 'https://www.someINEXISTENT.url',
    method: 'GET',
    gzip: true,
    headers: {
      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.96 Safari/537.36'
    },
    jar: true
  };

  // CREATE RESPONSE AND CHEERIO OBJECT
  let response = null;
  let $ = null;

  // SEND REQUEST TO URL, AND PARSE WITH CHEERIO
  response = await promisifiedRequest(urlOptions);
  $ = cheerio.load(response.body);

  // *** Do you really want to return the URL? Not something from the body of what you requested?
  return {
    yourUrl : url
  };
});

【讨论】:

  • 成功了!我刚刚添加了一个包含await 调用的try 块。然后我在返回之前从catch 块中抛出了error。我认为这是必要的,因为我需要抛出该错误以停止该函数并将错误发送给客户端。最后返回的 url 仅用于测试目的。非常感谢。
  • @cbdev420 - 太好了!很高兴有帮助,我还没有完成 Firebase,但文档说你可以返回一个承诺,所以......如果你想停止函数并将错误发送给客户端,你不需要捕获并重新抛出错误那一点。如果您想抛出一个 不同 错误而不是已经抛出的错误,您只需要捕获并重新抛出它。编码愉快!
  • 你是对的!即使没有 catch 块,Firebase 也会向我的客户抛出(错误代码 = 内部)。虽然,在我的函数日志中,它抱怨发生了 Unhandled error。所以无论如何我最好处理它。由于我们正在讨论这个主题,我正在考虑以“始终成功”的方式处理我的函数中的错误,即:会捕获错误但不会抛出异常,我会成功返回一个错误对象而不是定期响应并在客户端检查。客户总是会进入.then()。您认为这两种方法有什么(缺点)优势吗?
  • 我会仔细检查该结果,如果 Firebase 将错误返回给您的应用程序,则它不是未处理的,也不应该有未处理的拒绝错误。总是返回成功:我不喜欢这样做,在我看来,最好遵循单独的成功与失败路径。
  • 我会听从你的建议。非常感谢您的帮助!
猜你喜欢
  • 2020-10-03
  • 1970-01-01
  • 2019-02-11
  • 2016-01-26
  • 2018-12-20
  • 1970-01-01
相关资源
最近更新 更多