Firebase云功能中的嵌套HTTP请求

时间:2018-10-17 17:48:02

标签: javascript node.js firebase firebase-realtime-database google-cloud-functions

我正在使用HTTP触发的Firebase云功能来发出HTTP请求。我返回了一系列结果(来自Meetup.com的事件),并将每个结果推送到Firebase实时数据库。但是对于每个结果,我还需要对另一个附加信息(托管事件的组的类别)发出另一个HTTP请求,以将其折叠到我要为该事件推送到数据库的数据中。这些嵌套的请求导致云函数崩溃,并出现我无法理解的错误。

const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();

const request = require('request');

exports.foo = functions.https.onRequest(
    (req, res) => {
        var ref = admin.database().ref("/foo");
        var options = {
            url: "https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=****",
            json: true
        };
        return request(
            options,
            (error, response, body) => {
                if (error) {
                    console.log(JSON.stringify(error));
                    return res.status(500).end();
                }
                if ("results" in body) {
                    for (var i = 0; i < body.results.length; i++) {
                        var result = body.results[i];
                        if ("name" in result &&
                            "description" in result &&
                            "group" in result &&
                            "urlname" in result.group
                        ) {
                            var groupOptions = {
                                url: "https://api.meetup.com/" + result.group.urlname + "?sign=true&photo-host=public&key=****",
                                json: true
                            };
                            var categoryResult = request(
                                groupOptions,
                                (groupError, groupResponse, groupBody) => {
                                    if (groupError) {
                                        console.log(JSON.stringify(error));
                                        return null;
                                    }
                                    if ("category" in groupBody &&
                                        "name" in groupBody.category
                                    ) {
                                        return groupBody.category.name;
                                    }
                                    return null;
                                }
                            );
                            if (categoryResult) {
                                var event = {
                                    name: result.name,
                                    description: result.description,
                                    category: categoryResult
                                };
                                ref.push(event);
                            }
                        }
                    }
                    return res.status(200).send("processed events");
                } else {
                    return res.status(500).end();
                }
            }
        );
    }
);

函数崩溃,日志显示:

Error: Reference.push failed: first argument contains a function in property 'foo.category.domain._events.error' with contents = function (err) {
      if (functionExecutionFinished) {
        logDebug('Ignoring exception from a finished function');
      } else {
        functionExecutionFinished = true;
        logAndSendError(err, res);
      }
    }
    at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/@firebase/database/dist/index.node.cjs.js:1436:15)
    at /user_code/node_modules/firebase-admin/node_modules/@firebase/database/dist/index.node.cjs.js:1479:13
    at Object.forEach (/user_code/node_modules/firebase-admin/node_modules/@firebase/util/dist/index.node.cjs.js:837:13)
    at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/@firebase/database/dist/index.node.cjs.js:1462:14)
    at /user_code/node_modules/firebase-admin/node_modules/@firebase/database/dist/index.node.cjs.js:1479:13
    at Object.forEach (/user_code/node_modules/firebase-admin/node_modules/@firebase/util/dist/index.node.cjs.js:837:13)
    at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/@firebase/database/dist/index.node.cjs.js:1462:14)
    at /user_code/node_modules/firebase-admin/node_modules/@firebase/database/dist/index.node.cjs.js:1479:13
    at Object.forEach (/user_code/node_modules/firebase-admin/node_modules/@firebase/util/dist/index.node.cjs.js:837:13)
    at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/@firebase/database/dist/index.node.cjs.js:1462:14)

如果我省略了获取组类别的内容,则其余代码工作正常(只需将每个事件的名称和描述写入数据库,就没有嵌套请求)。那么正确的方法是什么?

3 个答案:

答案 0 :(得分:1)

我怀疑此问题是由于回调引起的。当您使用Firebase函数时,导出的函数应等待所有内容执行,或者返回一个在所有内容完成执行后便会解决的承诺。在这种情况下,导出的函数将在其余执行完成之前返回。

这是更多基于承诺的开始-

const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();

const request = require("request-promise-native");

    exports.foo = functions.https.onRequest(async (req, res) => {
    const ref = admin.database().ref("/foo");
    try {
        const reqEventOptions = {
            url:
                "https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=xxxxxx",
            json: true
        };
        const bodyEventRequest = await request(reqEventOptions);
        if (!bodyEventRequest.results) {
            return res.status(200).end();
        }
        await Promise.all(
            bodyEventRequest.results.map(async result => {
                if (
                    result.name &&
                    result.description &&
                    result.group &&
                    result.group.urlname
                ) {
                    const event = {
                        name: result.name,
                        description: result.description
                    };

                    // get group information
                    const groupOptions = {
                        url:
                            "https://api.meetup.com/" +
                            result.group.urlname +
                            "?sign=true&photo-host=public&key=xxxxxx",
                        json: true
                    };

                    const categoryResultResponse = await request(groupOptions);
                    if (
                        categoryResultResponse.category &&
                        categoryResultResponse.category.name
                    ) {
                        event.category = categoryResultResponse.category.name;
                    }

                    // save to the databse
                    return ref.push(event);
                }
            })
        );
        return res.status(200).send("processed events");
    } catch (error) {
        console.error(error.message);
    }
});

有关更改的快速概述-

  • 使用await和async调用来等待事情完成,而不是在回调中被触发(通常,async和await比.promise使用.then函数更容易阅读,因为执行顺序是代码的顺序)
  • 使用支持promise / await的request-promise-native(即await表示等待,直到promise返回,所以我们需要返回promise的东西)
  • 使用const并让vs. var作为变量;这扩大了变量的范围
  • 与其进行诸如if(is good)的检查{做美好的事情},而是使用if(isbad){return some error}做一个精瘦的事情。这使代码更易于阅读,并防止了许多嵌套的ifs而不知道它们的结尾
  • 使用Promise.all(),以便同时检索每个事件的类别

答案 1 :(得分:1)

您应该在代码中实现两个主要更改:

  • 由于request未返回诺言,因此您需要为request使用接口包装程序,例如request-promise,以便正确地链接不同的异步事件(请参阅道格对问题的评论) )
  • 由于您随后将使用request-promise多次(并行)调用不同的端点,因此需要使用Promise.all()来等待所有promise解析后再发送回响应。对于Firebase push()方法的不同调用也是如此。

因此,按照以下内容修改代码应该可以。

我让您通过修改它的方式来获得用于构造name对象的descriptionevent的值。 results数组中各项的顺序与promises中一项的顺序完全相同。因此,您应该知道,就能在name中获取descriptionresults.forEach(groupBody => {})的值,例如通过将这些值保存在全局数组中。


const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

var rp = require('request-promise');

exports.foo = functions.https.onRequest((req, res) => {
  var ref = admin.database().ref('/foo');
  var options = {
    url:
      'https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=****',
    json: true
  };
  rp(options)
    .then(body => {
      if ('results' in body) {
        const promises = [];
        for (var i = 0; i < body.results.length; i++) {
          var result = body.results[i];
          if (
            'name' in result &&
            'description' in result &&
            'group' in result &&
            'urlname' in result.group
          ) {
            var groupOptions = {
              url:
                'https://api.meetup.com/' +
                result.group.urlname +
                '?sign=true&photo-host=public&key=****',
              json: true
            };

            promises.push(rp(groupOptions));
          }
        }
        return Promise.all(promises);
      } else {
        throw new Error('err xxxx');
      }
    })
    .then(results => {
      const promises = [];

      results.forEach(groupBody => {
        if ('category' in groupBody && 'name' in groupBody.category) {
          var event = {
            name: '....',
            description: '...',
            category: groupBody.category.name
          };
          promises.push(ref.push(event));
        } else {
          throw new Error('err xxxx');
        }
      });
      return Promise.all(promises);
    })
    .then(() => {
      res.send('processed events');
    })
    .catch(error => {
      res.status(500).send(error);
    });
});

答案 2 :(得分:0)

我进行了一些更改并使其与Node 8一起使用。我将其添加到了package.json

"engines": {
    "node": "8"
}

基于R. Wright的answer和一些Firebase云功能示例代码,这就是现在的代码。

const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();

const request = require("request-promise-native");

exports.foo = functions.https.onRequest(
    async (req, res) => {
        var ref = admin.database().ref("/foo");
        var options = {
            url: "https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=****",
            json: true
        };
        await request(
            options,
            async (error, response, body) => {
                if (error) {
                    console.error(JSON.stringify(error));
                    res.status(500).end();
                } else if ("results" in body) {
                    for (var i = 0; i < body.results.length; i++) {
                        var result = body.results[i];
                        if ("name" in result &&
                            "description" in result &&
                            "group" in result &&
                            "urlname" in result.group
                        ) {
                            var groupOptions = {
                                url: "https://api.meetup.com/" + result.group.urlname + "?sign=true&photo-host=public&key=****",
                                json: true
                            };
                            var groupBody = await request(groupOptions);
                            if ("category" in groupBody && "name" in groupBody.category) {
                                var event = {
                                    name: result.name,
                                    description: result.description,
                                    category: groupBody.category.name
                                };
                                await ref.push(event);
                            }
                        }
                    }
                    res.status(200).send("processed events");
                }
            }
        );
    }
);