Javascript Firebase云函数中的嵌套HTTP请求

Javascript Firebase云函数中的嵌套HTTP请求,javascript,node.js,firebase,firebase-realtime-database,google-cloud-functions,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("fireba

我正在使用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();
                }
            }
        );
    }
);

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

我怀疑这个问题是由于回调造成的。当您使用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通常比承诺更容易阅读。然后函数的执行顺序就是代码的顺序)
  • 使用request-promise-native,它支持承诺/等待(即等待意味着等待,直到承诺返回,所以我们需要返回承诺的东西)
  • 使用常量和let vs.var作为变量;这改进了变量的范围
  • 不要像if(is good){do good things}那样进行检查,而是使用if(isbad){return some error}do good thin。这使代码更易于阅读,并防止出现大量嵌套的if,因为您不知道它们的结尾在哪里
  • 使用Promise.all()以便并行地检索每个事件的类别

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

  • 由于
    request
    不返回承诺,您需要为
    request
    使用接口包装器,以便正确链接不同的异步事件(参见Doug对您的问题的评论)
  • 由于随后将使用
    request promise
    多次(并行)调用不同的端点,因此需要使用
    promise.all()
    ,以便在发送回响应之前等待所有承诺的解决。对Firebase
    push()
    方法的不同调用也是如此
因此,按照以下行修改代码应该是可行的

我允许您以这样的方式修改它:您可以获得用于构造
事件
对象的
name
description
的值。
结果
数组中项目的顺序与
承诺
数组中项目的顺序完全相同。因此,您应该能够在
results.forEach(groupBody=>{})中获取
name
description
的值,例如,将这些值保存在全局数组中


const functions=require('firebase-functions');
const admin=require('firebase-admin');
admin.initializeApp();
var rp=要求(“要求-承诺”);
exports.foo=functions.https.onRequest((req,res)=>{
var ref=admin.database().ref('/foo');
变量选项={
网址:
'https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=***',,
json:true
};
rp(选项)
。然后(body=>{
如果(正文中的“结果”){
常量承诺=[];
对于(var i=0;i{
常量承诺=[];
results.forEach(groupBody=>{
if(groupBody中的“类别”和groupBody.category中的“名称”){
var事件={
姓名:‘……’,
说明:“…”,
类别:groupBody.category.name
};
承诺推送(参考推送(事件));
}否则{
抛出新错误('err xxxx');
}
});
返回承诺。全部(承诺);
})
.然后(()=>{
res.send(“已处理事件”);
})
.catch(错误=>{
资源状态(500)。发送(错误);
});
});

我做了一些更改,让它与节点8一起工作。我将此添加到我的
包.json

"engines": {
    "node": "8"
}
这就是代码现在的样子,基于R.Wright和一些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");
                }
            }
        );
    }
);
const functions=require(“firebase函数”);
const admin=require(“firebase管理员”);
admin.initializeApp();
const request=require(“请求承诺本机”);
exports.foo=functions.https.onRequest(
异步(请求、恢复)=>{
var ref=admin.database().ref(“/foo”);
变量选项={
url:“https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=**“,
json:true
};
等待请求(
选项,
异步(错误、响应、正文)=>{
如果(错误){
console.error(JSON.stringify(error));
res.status(500.end();
}else if(正文中的“结果”){
对于(var i=0;
"engines": {
    "node": "8"
}
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");
                }
            }
        );
    }
);