Ошибка SendGrid с функциями Firebase Cloud: «зависание сокета» - PullRequest
0 голосов
/ 14 января 2019

У меня есть облачная функция, запускаемая по событию pub / sub. Я использую sendgrid nodejs api. Основная идея - отправлять моим клиентам еженедельную статистику по электронной почте. Функция sendEmail () запускается для каждого клиента (80 раз). Но когда я проверяю журналы функций, я вижу, что 25-30 клиентских электронных писем отправляются успешно, а оставшиеся сообщения выдают такую ​​ошибку: "зависание сокета"

Я сократил весь код, чтобы показать основную часть, связанную с отправкой электронной почты. Вот последняя часть.

    // I shortened the whole function as it is a very long function.
    // The main and the last part is as below
    // I have nearly 80 clients and sendEmail function run for each client.

    function calcData(i, data) {
        return admin.database().ref('clientUrlClicks/' + data.key)
            .orderByChild('date')
            .startAt(dateStartEpox)
            .endAt(dateEndEpox)
            .once('value', urlClickSnap => {
                clients[i].clickTotalWeek = urlClickSnap.numChildren();
                clients[i].listTotalWeek = 0;
                admin.database().ref('clientImpressions/' + data.key)
                    .orderByKey()
                    .startAt(dateStart)
                    .endAt(dateEnd)
                    .once('value', snap => {
                        snap.forEach(function(impressionSnap) {
                            clients[i].listTotalWeek += impressionSnap.val();
                        })
                    }).then(resp => {
                        return sendEmail(i, clients[i]);
                    }).catch(err => {
                        console.log(err);
                    });
            }).catch(err => {
                clients[i].clickTotalWeek = 0;
                console.log(err);
            });
    }

   function sendEmail(i, data) {
        var options = {
            method: 'POST',
            url: 'https://api.sendgrid.com/v3/mail/send',
            headers:
            {
                'content-type': 'application/json',
                authorization: 'Bearer ' + sgApiKey
            },
            body:
            {
                personalizations:
                    [{
                        to: [{ email: data.email, name: data.name }],
                        dynamic_template_data:
                        {
                            dateStart: xxx,
                            dateEnd: xxx,
                        }
                    }],
                from: { email: 'info@xxx.com', name: 'xxx' },
                reply_to: { email: 'info@xxx.com', name: 'xxx' },
                template_id: 'd-f44eeexxxxxxxxxxxxx'
            },
            json: true
        };

        request(options, function (error, response, body) {
            if (error) {
                console.log("err: " + error);
                return;
            }
            return;
        });
    }

Edit:

В дополнение к ответам ниже, связанным с «правильной цепочкой обещаний», я также добавил все электронные письма и персонализации в массив «personalizations» как объект функции «sendEmail». Таким образом, вместо того, чтобы делать запрос на каждое письмо, я делаю один запрос. Теперь нет проблем.

Ответы [ 2 ]

0 голосов
/ 14 января 2019

Я вижу две проблемы с вашим кодом, связанные с цепочкой обещаний, которые могут быть причиной этой проблемы.

Во-первых, вы используете запрос с обратным вызовом в вашей функции sendEmail. Это просто не будет ждать завершения вашего сетевого вызова и вернет функцию. Теперь это создаст параллельные вызовы, и до того, как вы наберете 80 ваших клиентов, выполнение вашей облачной функции завершится. Решением будет использование библиотеки request-promise-native (https://github.com/request/request-promise-native)) с вашей библиотекой запросов. Таким образом, ваша функция sendEmail теперь станет

sendEmail (i, data) {
    .
    .
    .
    return rpn(options).then((d)=>{return d}).catch((e)=>{return console.log(e)})
}

Другое решение - использовать клиент sendgrid для nodejs, который просто вернет обещание, и вам не нужно будет использовать запрос. https://github.com/sendgrid/sendgrid-nodejs/tree/master/packages/mail

Вторая проблема заключается в том, что вы вызываете данные, считанные из базы данных, где вы также используете обратные вызовы вместо обещаний. Правильное решение будет:

function calcData(i, data) {
        return admin.database().ref('clientUrlClicks/' + data.key)
            .orderByChild('date')
            .startAt(dateStartEpox)
            .endAt(dateEndEpox)
            .once('value').then( urlClickSnap => {
                clients[i].clickTotalWeek = urlClickSnap.numChildren();
                clients[i].listTotalWeek = 0;
                return admin.database().ref('clientImpressions/' + data.key)
                    .orderByKey()
                    .startAt(dateStart)
                    .endAt(dateEnd)
                    .once('value').then( snap => {
                        snap.forEach(function(impressionSnap) {
                            clients[i].listTotalWeek += impressionSnap.val();
                        })
                        return sendEmail(i, clients[i]);
                    })
                    .catch(err => {
                        console.log(err);
                    });
            }).catch(err => {
                clients[i].clickTotalWeek = 0;
                console.log(err);
            });
    }

Это обеспечит, что вы вернете функцию calcData после завершения выполнения всех обещаний, связанных в цепочку.

Еще одна вещь, если вы вызываете calcData в цикле, а затем убедитесь, что вы храните все обещания в массиве и после вызова цикла Promise.all(promisesArray), чтобы ваша функция ожидала завершения всех выполнений.

0 голосов
/ 14 января 2019

Вы неправильно связываете цепочки обещаний и поэтому не возвращаете окончательное обещание в конце цепочки, что является обязательным для облачной функции.

Следующий набор изменений является первой попыткой решить эту проблему.

Кроме того, неясно, как вы вызываете Sendgrid и возвращаете обещание, возвращенное вызовом Sendgrid. Я бы посоветовал вам использовать метод send(), который возвращает Promise, как объяснено в документе Web-API Sendgrid v3 для Node.js, см. https://github.com/sendgrid/sendgrid-nodejs/tree/master/packages/mail.

function calcData(i, data) {
        //Declare clients aray here
        return admin.database().ref('clientUrlClicks/' + data.key)
            .orderByChild('date')
            .startAt(dateStartEpox)
            .endAt(dateEndEpox)
            .once('value')
            .then(urlClickSnap => {
                clients[i].clickTotalWeek = urlClickSnap.numChildren();
                clients[i].listTotalWeek = 0;
                return admin.database().ref('clientImpressions/' + data.key)  //Here you didn't return the promise
                    .orderByKey()
                    .startAt(dateStart)
                    .endAt(dateEnd)
                    .once('value');
             .then(snap => {
                     snap.forEach(function(impressionSnap) {
                         clients[i].listTotalWeek += impressionSnap.val();
                    })
                    return sendEmail(i, clients[i]);
             }).catch(err => {
                clients[i].clickTotalWeek = 0;
                console.log(err);
                return null;
            });
    }
...