SendGrid with Firebase Cloud Functions error: "socket hang up"
SendGrid with Firebase Cloud Functions error: "socket hang up"
我有一个由 pub/sub 事件触发的云函数。我使用 sendgrid nodejs api。主要想法是每周给我的客户发送一封统计电子邮件。每个客户端的 sendEmail() 函数 运行(80 次)。但是,当我检查功能日志时,我发现有 25-30 封客户端电子邮件已成功发送,但其余的却给出了错误:"socket hang up"
我缩短了整个代码以显示与发送电子邮件相关的主要部分。这是最后一部分。
// 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;
});
}
编辑:
除了下面与 "chaining the promises correctly" 相关的答案之外,我还将所有电子邮件和个性化设置作为 "sendEmail" 函数上的对象添加到 "personalizations" 数组。因此,我提出了一个请求,而不是对每封电子邮件提出请求。现在没问题了。
您没有正确链接 promise,因此没有 return在链接结束时进行最终 promise,这对于 Cloud Functions 是强制性的。
下面的一组修改是解决这个问题的第一次尝试。
另外,crystal 不清楚您如何调用 Sendgrid 和 return Promise return 通过 Sendgrid 调用。我建议您使用 send()
方法,它 return 是一个 Promise,如 Node.js 的 Sendgrid v3 Web API 文档中所述,请参阅 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;
});
}
我发现您的代码有两个与 promise 链接相关的问题,这可能会导致此问题。
首先是您在 sendEmail
函数中使用带回调的请求。这根本不会等待您的网络调用完成并 return 执行该功能。现在,这将并行建立调用,并且在您达到 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)})
}
其他解决方案是使用 nodejs 的 sendgrid 客户端,这将简单地 return 承诺,您不需要使用请求。 https://github.com/sendgrid/sendgrid-nodejs/tree/master/packages/mail
第二个问题是您要求从 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 returns。
如果您在循环中调用 calcData,还有一件事,请确保将所有承诺存储在一个数组中并在循环调用 Promise.all(promisesArray)
之后,以便您的函数等待所有执行完成.
我有一个由 pub/sub 事件触发的云函数。我使用 sendgrid nodejs api。主要想法是每周给我的客户发送一封统计电子邮件。每个客户端的 sendEmail() 函数 运行(80 次)。但是,当我检查功能日志时,我发现有 25-30 封客户端电子邮件已成功发送,但其余的却给出了错误:"socket hang up"
我缩短了整个代码以显示与发送电子邮件相关的主要部分。这是最后一部分。
// 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;
});
}
编辑:
除了下面与 "chaining the promises correctly" 相关的答案之外,我还将所有电子邮件和个性化设置作为 "sendEmail" 函数上的对象添加到 "personalizations" 数组。因此,我提出了一个请求,而不是对每封电子邮件提出请求。现在没问题了。
您没有正确链接 promise,因此没有 return在链接结束时进行最终 promise,这对于 Cloud Functions 是强制性的。
下面的一组修改是解决这个问题的第一次尝试。
另外,crystal 不清楚您如何调用 Sendgrid 和 return Promise return 通过 Sendgrid 调用。我建议您使用 send()
方法,它 return 是一个 Promise,如 Node.js 的 Sendgrid v3 Web API 文档中所述,请参阅 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;
});
}
我发现您的代码有两个与 promise 链接相关的问题,这可能会导致此问题。
首先是您在 sendEmail
函数中使用带回调的请求。这根本不会等待您的网络调用完成并 return 执行该功能。现在,这将并行建立调用,并且在您达到 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)})
}
其他解决方案是使用 nodejs 的 sendgrid 客户端,这将简单地 return 承诺,您不需要使用请求。 https://github.com/sendgrid/sendgrid-nodejs/tree/master/packages/mail
第二个问题是您要求从 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 returns。
如果您在循环中调用 calcData,还有一件事,请确保将所有承诺存储在一个数组中并在循环调用 Promise.all(promisesArray)
之后,以便您的函数等待所有执行完成.