转换AWS Lambda函数以使用promises?

Gur*_*ruJ 6 amazon-s3 node.js promise aws-lambda

我正在编写一个简单的HTTP"ping"函数,该函数使用AWS Lambda定期执行.它使用四个异步函数:http.get,S3.getObject,S3.putObject和nodemailer.sendMail.每个似乎都有一个稍微不同的回调模型.

在阅读了承诺后,我花了太多时间尝试将以下代码转换为使用Q承诺并且失败了.

对于我自己的教育和希望其他人的教育,我希望有人可以帮助我将其转换为使用承诺(不一定是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();
  }
};
Run Code Online (Sandbox Code Playgroud)

编辑:首次尝试使用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);
  });
};
Run Code Online (Sandbox Code Playgroud)

这不太奏效.结果日志是:

called 1
called 2
Last observed status: UP
called 3
called 4
Outcome of ping was:  undefined
ReferenceError: resolve is not defined
Run Code Online (Sandbox Code Playgroud)

Bri*_*ian 9

将典型的异步函数转换为承诺非常简单.我宁愿尝试演示如何转换它而不是编写代码,因为你没有从中学到任何东西.

通常使用节点,您将看到与此类似的内容:

  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
        }
    }
Run Code Online (Sandbox Code Playgroud)

包装异步函数的承诺通常如下所示:

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)
})
Run Code Online (Sandbox Code Playgroud)

要将结果传递给我们的"始终然后"方法,我们需要返回一个promise或一个值:

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)
    })
Run Code Online (Sandbox Code Playgroud)

我认为使用上面的模式应该适应代码示例中的大多数异步方法和事件,如果有任何特定的方法和事件让您放弃注释,我将尝试涵盖这些特定示例.

这是尝试将其转换为承诺 - 我很累,所以对任何混乱或错误表示道歉 - 还有很多可以完成的清理工作.

基本上我所做的就是尝试将代码分解为任务并将每个任务包装在一个承诺中.这样我们就可以根据需要解决/拒绝并链接它们.

'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();

        });

    }
};
Run Code Online (Sandbox Code Playgroud)


小智 6

AWS-SDK支持所有服务的本机承诺.有些需要额外的参数才能正确返回,比如Lambda.invoke().

你基本上会这样做

s3.putObject({ Key: 'key', Bucket: 'bucket' }).promise()
    .then(data => {
        // this is the same as the data callback parameter
    })
    .catch(error => {
        // handle your error
    })
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用async/ await:

const file = await s3.getObject(params).promise()
// do things with the result
Run Code Online (Sandbox Code Playgroud)

要快速访问实际文件(而不是元数据):

const file = JSON.parse(await s3.getObject(params).promise().then(res => res.Body));
Run Code Online (Sandbox Code Playgroud)

https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html


Bor*_*ier 5

为了"宣传"回调函数,imho,最简单,最简洁的方法就是使用蓝鸟.您只是不想编写粘合代码以简化代码,它会适得其反(并且容易出错).

来自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);
});
Run Code Online (Sandbox Code Playgroud)