AWS NodeJS Lambda email function not working correctly - node.js

I have a AWS Lambda that first gets data from an external API endpoint and then will loop the json data and will send email to each of the records. Here is the code:
var aws = require("aws-sdk");
var ses = new aws.SES({ region: "us-east-1" });
const https = require('https');
exports.handler = async function(event, context) {
return httprequest().then((data) => {
const response = {
statusCode: 200,
body: JSON.stringify(data),
};
data.forEach(function(item) {
// send email
sendEmailAsync(item);
});
return response;
});
};
function sendEmailAsync(item) {
return new Promise((resolve, reject) => {
console.log(`Sending email to: ${item.fullName} `);
let emailFrom = process.env.emailFrom;
const htmlBody = `
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<p>Hi ${item.fullName},</p>
<p>...</p>
</body>
</html>
`;
const textBody = `
Hi ${item.fullName},
...
`;
var params = {
Destination: {
ToAddresses: [item.email],
},
Message: {
Body: {
Html: {
Charset: "UTF-8",
Data: htmlBody
},
Text: {
Charset: "UTF-8",
Data: textBody
}
},
Subject: {
Charset: "UTF-8",
Data: "Test Email"
}
},
Source: emailFrom,
};
const sendPromise = ses
.sendEmail(params)
.promise();
sendPromise.then(data => {
console.log(data.MessageId);
context.done(null, "Success");
resolve(data.MessageId);
})
.catch(err => {
console.error(err, err.stack);
context.done(null, "Failed");
reject(err.message);
});
});
}
function httprequest() {
let host = process.env.endPointHost;
let path = process.env.endPointPath;
let apiKey = process.env.apiKey;
return new Promise((resolve, reject) => {
const options = {
host: host,
path: path + '?APIKey=' + apiKey,
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
};
const req = https.request(options, (res) => {
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(new Error('statusCode=' + res.statusCode));
}
var body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
res.on('end', function() {
try {
body = JSON.parse(Buffer.concat(body).toString());
}
catch (e) {
reject(e);
}
resolve(body);
});
});
req.on('error', (e) => {
reject(e.message);
});
// send the request
req.end();
});
}
Everything works fine except that when the sendEmailAsync(item); function is being called I get the console log but never get any result about the email being sent or not. Of course, I don't get any email but I'm pretty sure that the problem is that I have to await maybe the function inside the foreach statement but have tried some code and didn't work.
Any clue?

As the forEach won't give the expected result with the await function, we shall use Promise.all or for..of way of looping.
Let me give you the example by taking only the loop from your snippet.
Promise.all
await Promise.all(data.map(async (item) => {
try {
let res = await sendEmailAsync(item);
console.log(res)
} catch (error) {
console.log('Error: '+ error);
}
}))
for..of
for(let item of data) {
try {
let res = await sendEmailAsync(item);
console.log(res);
} catch (error) {
console.log('Error: ' + error);
}
}

Related

upload files to aws s3 from node js

I am using aws sdk to uplod user input image and then get the image link from aws and i will store the link in mongoDB. In that case when i run .upload() it is async.
const imgSRC = [];
for (let img of image) {
console.log(img);
const params = {
Bucket: process.env.AWS_BUCKET,
Key: `${img.originalname}_${userID}`,
Body: img.buffer,
};
s3.upload(params, (error, data) => {
if (error) {
console.log(error);
res.status(500).json({ msg: "server error" });
}
imgSRC.push(data.Location);
console.log(imgSRC);
});
}
const newPost = new Post({
userID: userID,
contentID: contentID,
posts: [
{
caption: caption,
data: imgSRC,
},
],
});
const post = await newPost.save();
in that case when the .save to mongodb run, there is no imgLinks from aws yet. How can i fix that things.
I've already tried async and it didn't work
You need to use Promise.all() in this manner
const uploadImage = (obj) => {
return new Promise((resolve, reject) => {
const params = {
Bucket: process.env.AWS_BUCKET,
Key: obj.key,
Body: obj.body,
}
s3.upload(params, (error, data) => {
if (error) {
console.log(error);
return reject(error);
}
return data;
});
})
}
const mainFunction = async () => {
const promises = [];
for (let img of image) {
const options = {
key: `${img.originalname}_${userID}`,
body: img.buffer
};
promises.push(uploadImage(options));
}
const result = await Promise.all(promises);
const imgSRC = result.map((r) => { return r.Location });
return imgSRC;
}
If you use await on s3.upload method you should remove the callback for this method.
try {
const data = await s3.upload(params);
imgSRC.push(data.Location);
console.log(imgSRC);
} catch(e) {
console.log(error);
res.status(500).json({ msg: "server error" });
}
Let me know if it works.

How to handle large file processing through AJAX and express

I'm uploading a PDF file using AJAX and express and sending the data to external REST API for some file processing.
Whenever I upload more than 3MB file I will get an error in AJAX before the REST API response.
I get correct results from the external REST API, but processing takes more time so AJAX is not receiving the success msg.
How do I handle this?
Tried with AXIOS and other promise handler but same result
module.exports = (req, res) =>
{
var pdf = [],
form = new formidable.IncomingForm(),
pdfFile,
dirname = process.cwd();
form.multiples = false;
// Upload directory for the images
form.uploadDir = path.join(dirname, 'tmp_uploads');
form.on('file', function(name, file) {
let token;
console.log('FORM ON')
if (req.cookies.userData.token) {
token = req.cookies.userData.token;
let buffer = null,
type = null,
filename = '',
renameFile = '';
// Read a chunk of the file.
buffer = readChunk.sync(file.path, 0, 262);
// filename = Date.now() + '-' + file.name;
filename = file.name;
renameFile = path.join(dirname, 'uploads/' + filename);
fs.rename(file.path, renameFile, function(err) {
if (err) throw err;
console.log('renamed complete');
pdfFile = path.join(dirname, 'uploads/' + filename);
let readFileStream = fs.createReadStream(pdfFile),
data = '',
formData = {};
formData = {
name: filename,
my_file: readFileStream,
Width: 1024,
Height: 768
};
function postData() {
// Setting URL and headers for request
var options = {
url: EXTERNALURL,
headers: {
"Authorization": 'bearer ' + token,
"content-type": "multipart/form-data"
},
formData: formData
};
// Return new promise
return new Promise(function(resolve, reject) {
// Do async job
request.post(options, function(err, resp, body) {
if (err) {
reject(err);
} else {
resolve(body);
}
})
})
}
function getData(url) {
// Return new promise
return new Promise(function(resolve, reject) {
// Do async job
request.get({ url: url, encoding: null }, function(err, resp, body) {
if (err) {
reject(err);
} else {
resolve(body);
}
})
})
}
var errHandler = function(err) {
console.log(err);
}
var filePath;
function main() {
var dataPromise = postData();
// Get user details after that get followers from URL
dataPromise.then(JSON.parse, errHandler)
.then(function(result) {
fData = result;
var fileName = fData.Id,
file_url = fData.PresentationZipUrl;
filePath = path.join(dirname, fData.HtmlPath);
// Do one more async operation here
var downloadPromise = getData(file_url).then();
return downloadPromise;
}, errHandler)
.then(function(data) {
//console.log(data);
var zip = new AdmZip(data);
if (!fs.existsSync(filePath)) {
fs.mkdirSync(filePath);
zip.extractAllTo(filePath, true);
}
}, errHandler)
.then(function(data) {
console.log('Done');
res.status(200).json({ 'status': "success" });
}, errHandler);
}
// console.log('before');
main();
}); // END RENAME FILE
} else { //
console.log('ERROR')
res.redirect('/');
}
});
form.on('error', function(err) {
console.log('Error occurred during processing - ' + err);
});
// Invoked when all the fields have been processed.
form.on('end', function() {
console.log('All the request fields have been processed.');
});
// Parse the incoming form fields.
form.parse(req, function(err, fields, files) {})}
AJAX:
$.ajax({
enter code here
url: '/presentation/uploadFiles',
method: 'post',
data: formData,
processData: false,
contentType: false,
timeout: 0}).done(function(d) {
console.log(d);
if (d.status == 'success') {
location.reload()
}}).fail(function(e, t) {
console.log(e);
console.log(t)})
.always(function() {
});

Use Firebase Function with error TypeError

I am very new to Node js, I just want to get the data from extenal xml from a website but I got an error from Firebase Function log TypeError: invalid media type. I think it come from when I try to do this task parseString(xml, function(err, result) { })
Anyone can help me, it will be great:
Here is my code on firebase function:
exports.getRate = functions.https.onRequest((req, res) => {
getRate = () => {
var url = "https://www.vietcombank.com.vn/ExchangeRates/ExrateXML.aspx";
https.get(url, function(res) {
var xml = "";
res.on('error', function(error){
console.log(error, 'get data error');
})
res.on("data", function(chunk) {
xml += chunk;
console.log(xml, 'xml file');
});
res.on("end", function() {
var date = "";
let rateAUD = {
code: 'AUD/VND',
buy: 0,
sell: 0
};
let rateUSD = {
code: 'USD/VND',
buy: 0,
sell: 0
};
parseString(xml, function(err, result) {
console.log(xml, 'xml file');
date = result.ExrateList.DateTime[0];
if (result.ExrateList.Exrate[0].$.CurrencyCode == "AUD") {
rateAUD.buy = result.ExrateList.Exrate[0].$.Buy;
rateAUD.sell = result.ExrateList.Exrate[0].$.Sell;
} else {
console.log("They change the database list");
}
if (result.ExrateList.Exrate[18].$.CurrencyCode == "USD") {
rateUSD.buy = result.ExrateList.Exrate[18].$.Buy;
rateUSD.sell = result.ExrateList.Exrate[18].$.Sell;
} else {
console.log("They change the database list");
}
console.log(rateAUD, rateUSD, 'get data');
uploadDataToServer(date, { rateAUD, rateUSD });
if(err) {
console.log(err);
}
});
});
});
};
function uploadDataToServer(date, { rateAUD, rateUSD }) {
var db = admin.firestore();
let data = { rateAUD, rateUSD };
data.timeStamp = date;
console.log('upload success');
db.collection("liveRate").add(data),then((err)=> {
console.log(err);
});
}
return res.status(200)
.type('application / json')
.send('hello')
});
'
When I run the same code on another Nodejs playground, it works well.
Here is the link: https://repl.it/repls/MaroonSlateblueProfiler
So weird!
Ps: my payment option is ON.
The problem is that the client is sending the server what may or may not be a valid media type in an encoding the server cannot understand (as per the Content-Encoding header the client packaged with the request message).
Please try to set the content-type to xml:
getRate = () => {
var options = {
hostname: "www.vietcombank.com.vn",
port: 443,
path: "/ExchangeRates/ExrateXML.aspx",
headers: {
'Content-Type': 'application/xml'
}
};
https.get(options, function(res) {
...
});
}

Converting AWS Lambda function to use promises?

I am writing a simple HTTP 'ping' function that is being periodically executed using AWS Lambda. It uses four asynchronous functions: http.get, S3.getObject, S3.putObject, and nodemailer.sendMail. Each seems to have a slightly different callback model.
After reading about promises, I spent way too much time trying to convert the following code to use Q promises and failed miserably.
For my own education and hopefully that of others, I was hoping someone could help me convert this to using promises (doesn't have to be Q):
'use strict';
var http = require('http');
var nodemailer = require('nodemailer');
var AWS = require('aws-sdk');
var s3 = new AWS.S3( { params: { Bucket: 'my-bucket' } } );
exports.handler = (event, context, callback) => {
var lastStatus;
var options = {
host: event.server.host,
port: event.server.port ? event.server.port : 80,
path: event.server.path ? event.server.path : '',
method: event.server.method ? event.server.method : 'HEAD',
timeout: 5000
};
var transporter = nodemailer.createTransport({
host: event.mail.host,
port: event.mail.port ? event.mail.port : 587,
auth: {
user: event.mail.user,
pass: event.mail.pass
}
});
var d = new Date();
var UTCstring = d.toUTCString();
// email templates
var downMail = {
from: event.mail.from,
to: event.mail.to,
subject: 'Lambda DOWN alert: SITE (' + event.server.host + ') is DOWN',
text: 'LambdaAlert DOWN:\r\nSITE (' + event.server.host + ') is DOWN as at ' + UTCstring + '.'
};
var upMail = {
from: event.mail.from,
to: event.mail.to,
subject: 'Lambda UP alert: SITE (' + event.server.host + ') is UP',
text: 'LambdaAlert UP:\r\nSITE (' + event.server.host + ') is UP as at ' + UTCstring + '.'
};
// Run async chain to ensure that S3 calls execute in proper order
s3.getObject( { Key: 'lastPingStatus' }, (err, data) => {
// get last status from S3
if (err) { lastStatus = "UP"; } else {
lastStatus = data.Body.toString();
console.log("Last observed status: " + lastStatus);
}
http_request(options, lastStatus);
});
function http_request(requestOptions, lastStatus) {
var req = http.request(requestOptions, function(res) {
if (res.statusCode == 200) {
if (lastStatus == "DOWN") {
console.log('Email up notice sending...');
transporter.sendMail(upMail, function(error, info) {
if (error) {
console.log("ERROR: " + error);
callback(null, "ERROR: " + error);
} else {
console.log('No further details available.');
callback(null, 'Up message sent');
}
});
}
s3.putObject({ Key: 'lastPingStatus', Body: 'UP', ContentType: 'text/plain' }, (error, data) => { console.log("Saved last state as UP"); });
callback(null, 'Website is OK.');
}
});
req.on('error', function(e) {
if (lastStatus == "UP") {
console.log('Email down notice sending...');
transporter.sendMail(downMail, function(error, info) {
if (error) {
console.log("ERROR: " + error);
callback(null, "ERROR: " + error);
} else {
console.log('No further details available.');
callback(null, 'Down message sent');
}
});
s3.putObject({ Key: 'lastPingStatus', Body: 'DOWN', ContentType: 'text/plain' }, (error, data) => { console.log("Saved last state as DOWN"); });
callback(null, 'Website is DOWN.');
}
});
req.end();
}
};
EDIT: First attempt at writing using promises:
'use strict';
var http = require('http');
var nodemailer = require('nodemailer');
var AWS = require('aws-sdk');
var s3 = new AWS.S3( { params: { Bucket: 'lambda-key-storage' } } );
exports.handler = (event, context, callback) => {
var lastStatus;
var options = {
host: event.server.host,
port: event.server.port ? event.server.port : 80,
path: event.server.path ? event.server.path : '',
method: event.server.method ? event.server.method : 'HEAD',
timeout: 5000
};
var transporter = nodemailer.createTransport({
host: event.mail.host,
port: event.mail.port ? event.mail.port : 587,
auth: {
user: event.mail.user,
pass: event.mail.pass
}
});
var d = new Date();
var UTCstring = d.toUTCString();
// email templates
var downMail = {
from: event.mail.from,
to: event.mail.to,
subject: 'Lambda DOWN alert: SITE (' + event.server.host + ') is DOWN',
text: 'LambdaAlert DOWN:\r\nSITE (' + event.server.host + ') is DOWN as at ' + UTCstring + '.'
};
var upMail = {
from: event.mail.from,
to: event.mail.to,
subject: 'Lambda UP alert: SITE (' + event.server.host + ') is UP',
text: 'LambdaAlert UP:\r\nSITE (' + event.server.host + ') is UP as at ' + UTCstring + '.'
};
var myProm = new Promise(function(resolve, reject) {
console.log("called 1");
s3.getObject( { Key: 'lastPingStatus' }, (err, data) => {
// get last status from S3
if (err) {
resolve("UP");
} else {
resolve(data.Body.toString());
}
});
})
.then(function(lastStatus) {
console.log("called 2");
console.log("Last observed status: " + lastStatus);
var req = http.request(options, function(res) {
resolve(res.statusCode);
});
req.on('error', function(e) {
reject(e);
});
req.end();
return "??";
})
.then(function(statusCode) {
console.log("called 3");
if (statusCode == 200) {
if (lastStatus == "DOWN") {
console.log('Email up notice sending...');
resolve("upTrigger");
} else {
resolve("upNoTrigger");
}
s3.putObject({ Key: 'lastPingStatus', Body: 'UP', ContentType: 'text/plain' }, (err, data) => { console.log("Saved last state as UP"); });
callback(null, 'Website is OK.');
}
})
.catch(function(err){
console.log("called 3 - error");
// Send mail notifying of error
if (lastStatus == "UP") {
console.log('Email down notice sending...');
resolve("downTrigger");
s3.putObject({ Key: 'lastPingStatus', Body: 'DOWN', ContentType: 'text/plain' }, (error, data) => { console.log("Saved last state as DOWN"); });
callback(null, 'Website is DOWN.');
return("downTrigger");
} else {
return "downNoTrigger";
}
})
.then(function(trigger) {
console.log("called 4");
if (trigger == "upTrigger") {
transporter.sendMail(upMail, (error, info) => {
if (error) {
console.log("ERROR: " + error);
callback(null, "ERROR: " + error);
} else {
console.log('Up message sent.');
callback(null, 'Up message sent');
}
});
} else if (trigger == "downTrigger") {
transporter.sendMail(downMail, (error, info) => {
if (error) {
console.log("ERROR: " + error);
callback(null, "ERROR: " + error);
} else {
console.log('Down message sent.');
callback(null, 'Down message sent');
}
});
}
console.log("Outcome of ping was: ", trigger);
});
};
This doesn't quite work. The result logs are:
called 1
called 2
Last observed status: UP
called 3
called 4
Outcome of ping was: undefined
ReferenceError: resolve is not defined
Converting your typical async function to a promise is pretty straight forward. I'd rather try and demonstrate how to convert it than write the code as you don't learn anything from that.
Usually with node you'll have something that looks similar to this:
doSomethingAsync(callback);
function doSomethingAsync(callback){
var err, result;
// Do some work
...
callback(err, result);
}
function callback(err, result){
if(err){
// Handle error
} else{
// Success so do something with result
}
}
A promise wrapping an async function generally looks something like this:
var myProm = new Promise(function(resolve, reject){
doSomethingAsync(function(err, result){
if(err){
reject(err);
} else{
resolve(result)
}
});
})
.then(function(result){
// Success so do something with result
console.log("Success:", result)
})
.catch(function(err){
// Handle error
console.log("Error: ", err);
})
.then(function(result){
// Where's my result? - result == undefined as we didn't return anything up the chain
console.log("I always execute but result is gone", result)
})
To pass the result down the chain to our "always then" method we need to return a promise or a value:
var myProm = new Promise(function(resolve, reject){
doSomethingAsync(function(err, result){
if(err){
reject(err);
} else{
resolve(result)
}
});
})
.then(function(result){
// Success so do something with result
console.log("Success:", result)
return result;
})
.catch(function(err){
// Handle error
console.log("Error: ", err);
return err;
})
.then(function(result){
// The err/result now gets passed down the chain :)
console.log("Oh there it is", result)
})
I think that using the above patterns should cater to most of the async methods and events in your code example if any particular ones are giving you trouble drop a comment in and I'll try to cover those specific examples.
Here's an attempt at converting it over to promises - I'm pretty tired so apologies about any mess or mistakes - also there's still plenty of cleanup that could be done.
Essentially what I've done is try to break down the code into tasks and wrap each of those tasks in a promise. That way we can resolve/reject and chain them as needed.
'use strict';
var http = require('http');
var nodemailer = require('nodemailer');
var AWS = require('aws-sdk');
var s3 = new AWS.S3( { params: { Bucket: 'my-bucket' } } );
exports.handler = function (event, context, callback) {
var lastStatus;
var options = {
host: event.server.host,
port: event.server.port ? event.server.port : 80,
path: event.server.path ? event.server.path : '',
method: event.server.method ? event.server.method : 'HEAD',
timeout: 5000
};
var transporter = nodemailer.createTransport({
host: event.mail.host,
port: event.mail.port ? event.mail.port : 587,
auth: {
user: event.mail.user,
pass: event.mail.pass
}
});
var d = new Date();
var UTCstring = d.toUTCString();
// email templates
var downMail = {
from: event.mail.from,
to: event.mail.to,
subject: 'Lambda DOWN alert: SITE (' + event.server.host + ') is DOWN',
text: 'LambdaAlert DOWN:\r\nSITE (' + event.server.host + ') is DOWN as at ' + UTCstring + '.'
};
var upMail = {
from: event.mail.from,
to: event.mail.to,
subject: 'Lambda UP alert: SITE (' + event.server.host + ') is UP',
text: 'LambdaAlert UP:\r\nSITE (' + event.server.host + ') is UP as at ' + UTCstring + '.'
};
// Run async chain to ensure that S3 calls execute in proper order
function getLastPingStatus(){
return new Promise(function(resolve, reject){
s3.getObject( { Key: 'lastPingStatus' }, function(err, data) {
// get last status from S3
if (err) {
lastStatus = "UP";
reject(lastStatus)
} else {
lastStatus = data.Body.toString();
resolve(lastStatus);
console.log("Last observed status: " + lastStatus);
}
});
})
}
getLastPingStatus()
.then(httpRequest)
.catch(httpRequest); // Otherwise a reject will throw an error
function sendMail(mail, status){ // status = "up" or "down" -
return new Promise(function(resolve, reject){
transporter.sendMail(mail, function(error, info) {
if (error) {
console.log("ERROR: " + error);
reject(null, "ERROR: " + error);
} else {
console.log('No further details available.');
resolve(null, status + ' message sent');
}
});
});
}
function saveStatus(up) {
return new Promise(function (resolve, reject) {
var saveOptions,
message;
// I didn't bother refactoring these as promises at they do the same thing regardless of outcome
if(up){
saveOptions = [{ Key: 'lastPingStatus', Body: 'UP', ContentType: 'text/plain' }, function(error, data) { console.log("Saved last state as UP"); }];
message = 'Website is OK.';
} else{
saveOptions = [{ Key: 'lastPingStatus', Body: 'DOWN', ContentType: 'text/plain' }, function(error, data) { console.log("Saved last state as DOWN"); }];
message = 'Website is DOWN.';
}
s3.putObject.apply(this, saveOptions);
callback(null, message);
});
}
function httpRequest(lastStatus) {
var requestOptions = options;
return new Promise (function (resolve, reject){
var req = http.request(requestOptions, function(res) {
if (res.statusCode == 200) {
if (lastStatus == "DOWN") {
console.log('Email up notice sending...');
sendMail(upMail, "Up")
.then(resolve, reject)
.then(saveStatus(true))
.then(callback)
}
}
});
req.on('error', function(e) {
if (lastStatus == "UP") {
console.log('Email down notice sending...');
sendmail(downMail, "Down")
.then(resolve, reject)
.then(saveStatus(false))
.then(callback)
}
});
req.end();
});
}
};
The AWS-SDK supports native promises, for all services. Some need additional parameters to return properly, like Lambda.invoke().
You would essentially do
s3.putObject({ Key: 'key', Bucket: 'bucket' }).promise()
.then(data => {
// this is the same as the data callback parameter
})
.catch(error => {
// handle your error
})
Or, you could use async/await:
const file = await s3.getObject(params).promise()
// do things with the result
For quick access to the actual file (and not metadata):
const file = JSON.parse(await s3.getObject(params).promise().then(res => res.Body));
https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html
In order to "promisify" callback function, imho, the easiest and cleaner way is to use bluebird. You just don't want to write glue code in order to simplify your code, it's counter productive (and it's error prone).
From the doc :
var Promise = require("bluebird");
var readFile = Promise.promisify(require("fs").readFile);
readFile("myfile.js", "utf8").then(function(contents) {
return eval(contents);
}).then(function(result) {
console.log("The result of evaluating myfile.js", result);
}).catch(SyntaxError, function(e) {
console.log("File had syntax error", e);
//Catch any other error
}).catch(function(e) {
console.log("Error reading file", e);
});
After reading slaughtr's answer I decided to do it like this, for doing some data saving when pressing the AWS IoT button:
var AWS = require("aws-sdk");
var iot = new AWS.Iot();
exports.handler = (event, context, callback) => {
iot.listThings({
attributeName: 'dsn',
attributeValue: event.serialNumber,
maxResults: 1
})
.promise()
.then(response => {
return iot.listThingGroupsForThing({thingName: response.things[0].thingName}).promise();
})
.then(groupsList => insertRecordIntoDDB(date, serialNumber, groupsList.thingGroups[0].groupName))
.catch(err => console.log(err))
};
and shortly after I decided to compress it even further with async/await
exports.handler = async (event, context, callback) => {
var eventText = JSON.stringify(event, null, 2);
var thingsList = await iot.listThings({
attributeName: 'dsn',
attributeValue: event.serialNumber,
maxResults: 1
}).promise()
var groupsList = await iot.listThingGroupsForThing({
'thingName': thingsList.things[0].thingName
}).promise();
insertRecordIntoDDB(date, serialNumber, groupsList.thingGroups[0].groupName)
};
I'm still fairly new at this async programming stuff so I'm not sure what I should like the most. Promise chaining can get a bit spaghetti-like, while async await only helps mask all that into something that's easier to comprehend
Using node http promisified to call external api in aws lambda.
exports.handler = async (event) => {
return httprequest().then((data) => {
const response = {
statusCode: 200,
body: JSON.stringify(data),
};
return response;
});
};
function httprequest() {
return new Promise((resolve, reject) => {
const options = {
host: 'jsonplaceholder.typicode.com',
path: '/todos',
port: 443,
method: 'GET'
};
const req = http.request(options, (res) => {
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(new Error('statusCode=' + res.statusCode));
}
var body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
res.on('end', function() {
try {
body = JSON.parse(Buffer.concat(body).toString());
} catch(e) {
reject(e);
}
resolve(body);
});
});
req.on('error', (e) => {
reject(e.message);
});
// send the request
req.end();
});
}

nodejs - How to promisify http.request? reject got called two times

I'm trying to wrap http.request into Promise:
new Promise(function(resolve, reject) {
var req = http.request({
host: '127.0.0.1',
port: 4000,
method: 'GET',
path: '/api/v1/service'
}, function(res) {
if (res.statusCode < 200 || res.statusCode >= 300) {
// First reject
reject(new Error('statusCode=' + res.statusCode));
return;
}
var body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
res.on('end', function() {
try {
body = JSON.parse(Buffer.concat(body).toString());
} catch(e) {
reject(e);
return;
}
resolve(body);
});
});
req.on('error', function(err) {
// Second reject
reject(err);
});
req.write('test');
}).then(function(data) {
console.log(data);
}).catch(function(err) {
console.log(err);
});
If I recieve errornous statusCode from remote server it will call First reject and after a bit of time Second reject. How to make properly so it calls only single reject (I think First reject is proper one in this case)? I think I need to close res myself, but there is no close() method on ClientResponse object.
UPD:
Second reject triggers very rarely - why?
Your code is almost fine. To restate a little, you want a function that wraps http.request with this form:
function httpRequest(params, postData) {
return new Promise(function(resolve, reject) {
var req = http.request(params, function(res) {
// on bad status, reject
// on response data, cumulate it
// on end, parse and resolve
});
// on request error, reject
// if there's post data, write it to the request
// important: end the request req.end()
});
}
Notice the addition of params and postData so this can be used as a general purpose request. And notice the last line req.end() -- which must always be called -- was missing from the OP code.
Applying those couple changes to the OP code...
function httpRequest(params, postData) {
return new Promise(function(resolve, reject) {
var req = http.request(params, function(res) {
// reject on bad status
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(new Error('statusCode=' + res.statusCode));
}
// cumulate data
var body = [];
res.on('data', function(chunk) {
body.push(chunk);
});
// resolve on end
res.on('end', function() {
try {
body = JSON.parse(Buffer.concat(body).toString());
} catch(e) {
reject(e);
}
resolve(body);
});
});
// reject on request error
req.on('error', function(err) {
// This is not a "Second reject", just a different sort of failure
reject(err);
});
if (postData) {
req.write(postData);
}
// IMPORTANT
req.end();
});
}
This is untested, but it should work fine...
var params = {
host: '127.0.0.1',
port: 4000,
method: 'GET',
path: '/api/v1/service'
};
// this is a get, so there's no post data
httpRequest(params).then(function(body) {
console.log(body);
});
And these promises can be chained, too...
httpRequest(params).then(function(body) {
console.log(body);
return httpRequest(otherParams);
}).then(function(body) {
console.log(body);
// and so on
});
I know this question is old but the answer actually inspired me to write a modern version of a lightweight promisified HTTP client. Here is a new version that:
Use up to date JavaScript syntax
Validate input
Support multiple methods
Is easy to extend for HTTPS support
Will let the client decide on how to deal with response codes
Will also let the client decide on how to deal with non-JSON bodies
Code below:
function httpRequest(method, url, body = null) {
if (!['get', 'post', 'head'].includes(method)) {
throw new Error(`Invalid method: ${method}`);
}
let urlObject;
try {
urlObject = new URL(url);
} catch (error) {
throw new Error(`Invalid url ${url}`);
}
if (body && method !== 'post') {
throw new Error(`Invalid use of the body parameter while using the ${method.toUpperCase()} method.`);
}
let options = {
method: method.toUpperCase(),
hostname: urlObject.hostname,
port: urlObject.port,
path: urlObject.pathname
};
if (body) {
options.headers = {'Content-Length':Buffer.byteLength(body)};
}
return new Promise((resolve, reject) => {
const clientRequest = http.request(options, incomingMessage => {
// Response object.
let response = {
statusCode: incomingMessage.statusCode,
headers: incomingMessage.headers,
body: []
};
// Collect response body data.
incomingMessage.on('data', chunk => {
response.body.push(chunk);
});
// Resolve on end.
incomingMessage.on('end', () => {
if (response.body.length) {
response.body = response.body.join();
try {
response.body = JSON.parse(response.body);
} catch (error) {
// Silently fail if response is not JSON.
}
}
resolve(response);
});
});
// Reject on request error.
clientRequest.on('error', error => {
reject(error);
});
// Write request body if present.
if (body) {
clientRequest.write(body);
}
// Close HTTP connection.
clientRequest.end();
});
}
There are other ways as well but here you can find a simple way to make http.request as a promise or async/await type.
Here is a working sample code:
var http = require('http');
function requestAsync(name) {
return new Promise((resolve, reject) => {
var post_options = {
host: 'restcountries.eu',
port: '80',
path: `/rest/v2/name/${name}`,
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};
let post_req = http.request(post_options, (res) => {
res.setEncoding('utf8');
res.on('data', (chunk) => {
resolve(chunk);
});
res.on("error", (err) => {
reject(err);
});
});
post_req.write('test');
post_req.end();
});
}
//Calling request function
//:1- as promise
requestAsync("india").then(countryDetails => {
console.log(countryDetails);
}).catch((err) => {
console.log(err);
});
//:2- as await
let countryDetails = await requestAsync("india");
After reading all of these and a few articles, I thought I'd post a sort of "general" solution that handles both http and https:
const http = require("http");
const https = require("https");
const url_obj = require("url");
const request = async (url_string, method = "GET", postData = null) => {
const url = url_obj.parse(url_string);
const lib = url.protocol=="https:" ? https : http;
const params = {
method:method,
host:url.host,
port: url.port || url.protocol=="https:" ? 443 : 80,
path: url.path || "/"
};
return new Promise((resolve, reject) => {
const req = lib.request(params, res => {
if (res.statusCode < 200 || res.statusCode >= 300) {
return reject(new Error(`Status Code: ${res.statusCode}`));
}
const data = [];
res.on("data", chunk => {
data.push(chunk);
});
res.on("end", () => resolve(Buffer.concat(data).toString()));
});
req.on("error", reject);
if (postData) {
req.write(postData);
}
req.end();
});
}
You could use like this:
request("google.com").then(res => console.log(res)).catch(err => console.log(err))
This is heavily inspired by this article, but replaces the hacky url parsing with the built in api.
Hope this help.
const request = require('request');
async function getRequest() {
const options = {
url: 'http://example.com',
headers: {
'Authorization': 'Bearer xxx'
}
};
return new Promise((resolve, reject) => {
return request(options, (error, response, body) => {
if (!error && response.statusCode == 200) {
const json = JSON.parse(body);
return resolve(json);
} else {
return reject(error);
}
});
})
}
It's easier for you to use bluebird api, you can promisify request module and use the request function async as a promise itself, or you have the option of using the module request-promise, that makes you to not working to creating a promise but using and object that already encapsulates the module using promise, here's an example:
var rp = require('request-promise');
rp({host: '127.0.0.1',
port: 4000,
method: 'GET',
path: '/api/v1/service'})
.then(function (parsedBody) {
// GET succeeded...
})
.catch(function (err) {
// GET failed...
});

Resources