Вложенные HTTP-запросы в облачной функции Firebase - PullRequest
0 голосов
/ 17 октября 2018

Я использую запускаемую по 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 голосов
/ 17 октября 2018

Есть два основных изменения, которые вы должны внести в свой код:

  • Поскольку request не возвращает обещание, вам нужно использовать интерфейсную оболочку для request, например request-promise для правильной цепочки различных асинхронных событий (см. Комментарий Дуга к вашему вопросу)
  • Поскольку затем вы будете вызывать несколько раз (параллельно) разные конечные точки с request-promise, вам нужно использоватьPromise.all() чтобы дождаться разрешения всех обещаний перед отправкой ответа.Это также относится и к различным вызовам метода Firebase push().

Поэтому изменение вашего кода в следующих строках должно работать.

Я позволю вам изменить его втаким образом вы получаете значения name и description, используемые для создания объекта event.Порядок элементов в массиве results точно такой же, как у элемента promises.Таким образом, вы должны иметь возможность, зная это, получать значения name и description в пределах results.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);
    });
});
0 голосов
/ 17 октября 2018

Я внес некоторые изменения и начал работать с Node 8. Я добавил это к своему package.json:

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

И вот как теперь выглядит код, основанный на * 1005 Р. Райта* ответ и пример кода для облачной функции 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");
                }
            }
        );
    }
);
0 голосов
/ 17 октября 2018

Я подозреваю, что эта проблема связана с обратными вызовами.Когда вы используете функции 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, как правило, гораздо легче читать, чем обещания с функциями .then, поскольку порядок выполнения - это порядокcode)
  • Используется запрос-обещание-нативный, который поддерживает обещания / ожидание (т. е. ожидание означает ожидание возврата обещания, поэтому нам нужно что-то, что возвращает обещание)
  • Используется const и let vs.var для переменных;это улучшает область видимости переменных
  • Вместо того, чтобы делать проверки, например, если (хорошо) {делать хорошие вещи}, использовать if (isbad) {возвращать какую-то ошибку} делать хорошие слияния.Это облегчает чтение кода и предотвращает множество вложенных if, если вы не знаете, где они заканчиваются.
  • Используйте Promise.all (), поэтому получение категорий для каждого события выполняется параллельно
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...