node.js - Converting AWS Lambda function to use promises? -
i writing simple http 'ping' function being periodically executed using aws lambda. uses 4 asynchronous functions: http.get, s3.getobject, s3.putobject, , nodemailer.sendmail. each seems have different callback model.
after reading promises, spent way time trying convert following code use q promises , failed miserably.
for own education , of others, hoping me convert using promises (doesn't have 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 + ') down', text: 'lambdaalert down:\r\nsite (' + event.server.host + ') down @ ' + utcstring + '.' }; var upmail = { from: event.mail.from, to: event.mail.to, subject: 'lambda alert: site (' + event.server.host + ') up', text: 'lambdaalert up:\r\nsite (' + event.server.host + ') @ ' + utcstring + '.' }; // run async chain ensure s3 calls execute in proper order s3.getobject( { key: 'lastpingstatus' }, (err, data) => { // last status 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 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 up"); }); callback(null, 'website 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 down"); }); callback(null, 'website down.'); } }); req.end(); } };
edit: first attempt @ 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 + ') down', text: 'lambdaalert down:\r\nsite (' + event.server.host + ') down @ ' + utcstring + '.' }; var upmail = { from: event.mail.from, to: event.mail.to, subject: 'lambda alert: site (' + event.server.host + ') up', text: 'lambdaalert up:\r\nsite (' + event.server.host + ') @ ' + utcstring + '.' }; var myprom = new promise(function(resolve, reject) { console.log("called 1"); s3.getobject( { key: 'lastpingstatus' }, (err, data) => { // last status 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 notice sending...'); resolve("uptrigger"); } else { resolve("upnotrigger"); } s3.putobject({ key: 'lastpingstatus', body: 'up', contenttype: 'text/plain' }, (err, data) => { console.log("saved last state up"); }); callback(null, 'website 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 down"); }); callback(null, 'website 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. result logs are:
called 1 called 2 last observed status: called 3 called 4 outcome of ping was: undefined referenceerror: resolve not defined
converting typical async function promise pretty straight forward. i'd rather try , demonstrate how convert write code don't learn that.
usually node you'll have looks similar this:
dosomethingasync(callback); function dosomethingasync(callback){ var err, result; // work ... callback(err, result); } function callback(err, result){ if(err){ // handle error } else{ // success result } }
a promise wrapping async function looks this:
var myprom = new promise(function(resolve, reject){ dosomethingasync(function(err, result){ if(err){ reject(err); } else{ resolve(result) } }); }) .then(function(result){ // success result console.log("success:", result) }) .catch(function(err){ // handle error console.log("error: ", err); }) .then(function(result){ // where's result? - result == undefined didn't return chain console.log("i execute result gone", result) })
to pass result down chain our "always then" method need return promise or value:
var myprom = new promise(function(resolve, reject){ dosomethingasync(function(err, result){ if(err){ reject(err); } else{ resolve(result) } }); }) .then(function(result){ // success result console.log("success:", result) return result; }) .catch(function(err){ // handle error console.log("error: ", err); return err; }) .then(function(result){ // err/result gets passed down chain :) console.log("oh there is", result) })
i think using above patterns should cater of async methods , events in code example if particular ones giving trouble drop comment in , i'll try cover specific examples.
here's attempt @ converting on promises - i'm pretty tired apologies mess or mistakes - there's still plenty of cleanup done.
essentially i've done try break down code tasks , wrap each of tasks in promise. way can resolve/reject , chain them 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 + ') down', text: 'lambdaalert down:\r\nsite (' + event.server.host + ') down @ ' + utcstring + '.' }; var upmail = { from: event.mail.from, to: event.mail.to, subject: 'lambda alert: site (' + event.server.host + ') up', text: 'lambdaalert up:\r\nsite (' + event.server.host + ') @ ' + utcstring + '.' }; // run async chain ensure s3 calls execute in proper order function getlastpingstatus(){ return new promise(function(resolve, reject){ s3.getobject( { key: 'lastpingstatus' }, function(err, data) { // last status 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 reject throw 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; // didn't bother refactoring these promises @ same thing regardless of outcome if(up){ saveoptions = [{ key: 'lastpingstatus', body: 'up', contenttype: 'text/plain' }, function(error, data) { console.log("saved last state up"); }]; message = 'website ok.'; } else{ saveoptions = [{ key: 'lastpingstatus', body: 'down', contenttype: 'text/plain' }, function(error, data) { console.log("saved last state down"); }]; message = 'website 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 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(); }); } };
Comments
Post a Comment