带有Firebase云函数的SendGrid错误:“0”;“插座挂起”;

带有Firebase云函数的SendGrid错误:“0”;“插座挂起”;,firebase,google-cloud-functions,sendgrid-api-v3,Firebase,Google Cloud Functions,Sendgrid Api V3,我有一个由发布/订阅事件触发的云函数。我使用sendgrid节点api。主要的想法是每周给我的客户发一封统计邮件。sendEmail()函数为每个客户端运行(80次)。但当我检查功能日志时,我发现25-30个客户端电子邮件发送成功,但其余的则出现错误:“套接字挂起” 我缩短了整个代码,以显示与发送电子邮件相关的主要部分。这是最后一部分 // I shortened the whole function as it is a very long function. // The m

我有一个由发布/订阅事件触发的云函数。我使用sendgrid节点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;
        });
    }
编辑:


除了下面有关“正确链接承诺”的答案外,我还将所有电子邮件和个性化设置添加到“personalizations”数组中,作为“sendEmail”功能的对象。因此,我对每封电子邮件都提出一个请求,而不是一个请求。现在没问题。

您没有正确链接承诺,因此没有在链接结束时返回最终承诺,这对于云函数是必需的

以下一组修改是解决此问题的首次尝试

此外,如何调用Sendgrid并返回Sendgrid调用返回的承诺还不十分清楚。我建议您使用
send()
方法,该方法返回一个承诺,如Sendgrid v3 Web API for Node.js的文档中所述,请参阅


我发现您的代码中有两个与承诺链接相关的问题,这可能是导致此问题的原因

首先,您正在
sendmail
函数中使用带有回调的请求。这将不会等待网络调用完成并返回函数。现在,这将并行地建立调用,在达到80个客户端计数之前,云函数的执行将完成。解决方案是将
request promise native
()库与您的请求库一起使用。因此,您的sendEmail功能现在将变为

sendEmail (i, data) {
    .
    .
    .
    return rpn(options).then((d)=>{return d}).catch((e)=>{return console.log(e)})
}
另一种解决方案是使用sendgrid client for nodejs,它将简单地返回承诺,而您不需要使用请求

第二个问题是调用从firebase读取的数据,在firebase中使用回调而不是承诺。正确的解决方案是:

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)
,以便函数等待所有执行完成

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);
            });
    }