【问题标题】:Returning promises won't work for AWS SDK返回承诺不适用于 AWS 开发工具包
【发布时间】:2020-07-02 17:09:54
【问题描述】:

我创建了一个 API,它调用 get cloudWatch AWS API 并返回可以在我的应用程序上绘制的数据点。我对每个包都有单独的路由(如下面的路由代码所示)。此 API 使用 REST MVC 方法。

所以我正在用我的函数做几件事。

  1. 从 SQLite3 数据库中读取 EC2 实例数据以获取 有关正在运行的实例的信息(IP、instance_id、 instance_launchtime) 以便我可以将其放入所需的参数中 用于 AWS 开发工具包中的 getMetricStatistics API。

  2. 然后将来自第 1 步的数据放入一个参数数组(3 个参数以 3 个不同的度量数据点响应)。这会循环遍历每个参数,将其插入到 getMetricStatistics API(一个接一个,因为 getMetricStatistics 不会一次接受多个指标)以获取该实例的数据点并将它们推送到一个数组中。

我相信数据库是异步的,这就是我对它附加承诺的原因。 当我将端点加载到浏览器中时,它只会继续加载并且不会显示任何数据。但是,当我刷新页面时,它会正确显示所有结果...

这是我的 API 控制器:

// Return results sent from Cloud Watch API
const InsightModel = require('../models/insight.model.js');
const cloudWatch = InsightModel.cloudWatch;
const CWParams = InsightModel.CWParams;
const packageById = InsightModel.packageById;

let cpuUtilParam;
let cpuCBParam;
let cpuCUParam;
let insightParams = [];
let metricResults = [];
exports.getAnalytics = (req, res) => {
  const currentDate = new Date().toISOString();
  let promise1 = new Promise((resolve, reject) => {
    packageById(req.params.packageKey, (err, data) => {
      if (err) {
        reject(
          res.status(500).send({
            message:
              err.message ||
              'Error while getting the insight configuration data.',
          })
        );
      } else {
        cpuUtilParam = new CWParams(
          currentDate,
          'CPUUtilization',
          'AWS/EC2',
          data[0].launch_time,
          data[0].instance_id
        );
        cpuCBParam = new CWParams(
          currentDate,
          'CPUCreditBalance',
          'AWS/EC2',
          data[0].launch_time,
          data[0].instance_id
        );
        cpuCUParam = new CWParams(
          currentDate,
          'CPUCreditUsage',
          'AWS/EC2',
          data[0].launch_time,
          data[0].instance_id
        );
        insightParams = [cpuUtilParam, cpuCBParam, cpuCUParam];
          resolve(insightParams);
      }
    });
  })
  let promise2 = new Promise((resolve, reject) => {
    insightParams.forEach(metric => {
      cloudWatch.getMetricStatistics(metric, function(err, data) {
        if (err) {
          reject(
            res.status(500).send({
              messaage:
                err.message ||
                'Error occured while running cloudWatch getMetricStatistcs API: ',
            })
          );
        } else {
          metricResults.push(data);
          if (metricResults.length === insightParams.length)
            resolve(metricResults);
        }
      });
    });
  });

  Promise.all([promise1, promise2])
    .then(metricResults => {
      res.send(metricResults);
      console.log('AWS CW API successful');
    })
    .catch(err => {
      res.status(500).send({
        messaage:
          err.message ||
          'Error occured while reading in a promise from cloudWatch getMetricStatistcs API: ',
      })
    });
  metricResults = [];
};

API 的模型:

// Call AWS Cost Explorer API
const AWS = require('aws-sdk');
const config = require('./AWSconfig');
const database = require('./db');

const insightdb = database.insightdb;

AWS.config.update({
  accessKeyId: config.accessKeyId,
  secretAccessKey: config.secretAccessKey,
  region: config.region,
});

//Linking AWS CloudWatch Service
var cloudWatch = new AWS.CloudWatch();

const packageById = (packageId, callback) => {
  insightdb.all(
    'SELECT * FROM ec2Instance WHERE package_id == ?',
    packageId,
    (err, rows) => {
      if (err) {
        callback(err, null);
      } else {
        callback(null, rows);
      }
    }
  );
};

// Parameter class to feed into the CloudWatch getMetricStatistics function
const CWParams = function(reqDate, metricName,service,launchTime,instanceId) {
  (this.EndTime = reqDate) /* required */,
    (this.MetricName = metricName) /* required */,
    (this.Namespace = service) /* required */,
    (this.Period = 3600) /* required */,
    (this.StartTime = launchTime) /* ${createDate}`, required */,
    (this.Dimensions = [
      {
        Name: 'InstanceId' /* required */,
        Value: instanceId /* required */,
      },
    ]),
    (this.Statistics = ['Maximum']);
};

//Exports variables to the controller (so they can be re-used)
module.exports = { cloudWatch, CWParams, packageById };

API 的路由:

module.exports = app => {
  const insight = require('../controllers/insight.controller.js');
  app.get('/insights/aws/:packageKey', insight.getAnalytics);
};

【问题讨论】:

    标签: javascript node.js api promise aws-sdk


    【解决方案1】:

    就目前而言,在第二个 Promise 构造函数中,insightParams 保证尚未组合,因为 insightParams = [.....] 位于异步调用的回调中。因此,程序流程需要确保所有“promise2”的东西只有在“promise1”完成后才会发生。

    如果异步函数在尽可能低的级别被“承诺”,那么在更高级别的代码中事情会变得简单得多。所以在模型中做两件事:

    • 承诺cloudWatch.getMetricStatistics()
    • 编写 packageById() 以返回 Promise 而不是接受回调。

    模型因此变为:

    const AWS = require('aws-sdk'); // no change
    const config = require('./AWSconfig'); // no change
    const database = require('./db'); // no change
    
    const insightdb = database.insightdb; // no change
    
    AWS.config.update({
        accessKeyId: config.accessKeyId,
        secretAccessKey: config.secretAccessKey,
        region: config.region
    }); // no change
    
    var cloudWatch = new AWS.CloudWatch(); // no change
    
    // Promisify cloudWatch.getMetricStatistics() as  cloudWatch.getMetricStatisticsAsync().
    cloudWatch.getMetricStatisticsAsync = (metric) => {
        return new Promise((resolve, reject) => {
            cloudWatch.getMetricStatistics(metric, function(err, data) {
                if (err) {
                    if(!err.message) { // Probably not necessary but here goes ...
                        err.message = 'Error occured while running cloudWatch getMetricStatistcs API: ';
                    }
                    reject(err); // (very necessary)
                } else {
                    resolve(data);
                }
            });
        });
    };
    
    // Ensure that packageById() returns Promise rather than accepting a callback.
    const packageById = (packageId) => {
        return new Promise((resolve, reject) => {
            insightdb.all('SELECT * FROM ec2Instance WHERE package_id == ?', packageId, (err, rows) => {
                if (err) {
                    reject(err);
                } else {
                    resolve(rows);
                }
            });
        });
    };
    

    现在getAnalytics()可以这样写:

    exports.getAnalytics = (req, res) => {
        packageById(req.params.packageKey)
        .then(data => {
            const currentDate = new Date().toISOString();
            let insightParams = [
                new CWParams(currentDate, 'CPUUtilization', 'AWS/EC2', data[0].launch_time, data[0].instance_id),
                new CWParams(currentDate, 'CPUCreditBalance', 'AWS/EC2', data[0].launch_time, data[0].instance_id),
                new CWParams(currentDate, 'CPUCreditUsage', 'AWS/EC2', data[0].launch_time, data[0].instance_id)
            ];
            // Composition of `insightParams` is synchronous so you can continue 
            // with the `cloudWatch.getMetricStatisticsAsync()` stuff inside the same .then().
            return Promise.all(insightParams.map(metric => cloudWatch.getMetricStatisticsAsync(metric))); // Simple because of the Promisification above.
        }, err => {
            // This callback handles error from packageById() only,
            // and is probably unnecessary but here goes ...
            if(!err.message) {
                err.message = 'Error while getting the insight configuration data.';
            }
            throw err;
        })
        .then(metricResults => {
            res.send(metricResults);
            console.log('AWS CW API successful');
        })
        .catch(err => {
            // Any async error arising above will drop through to here.
            res.status(500).send({
                'message': err.message
            }));
        });
    };
    

    请注意,不需要多个带有res.status(500).send() 的捕获。沿着 Promise 链向下传播的错误允许单个终端 .catch()

    【讨论】:

    • 这是我得到的回复:{ "message": "data is not defined" }
    • 已编辑 - 再试一次。
    • 还有一个小修改。 insightParams 未声明。
    • 另外,我并不完全理解这段代码(虽然它很漂亮)。流程是什么(就像在控制器中一样)。 packageById 承诺是否首先得到解决?如果是这样,你怎么会在最后返回一个 Promise.all 并传入 map/getMetricStatisticsAsync。我的理解是 Promise.all 在一个数组中接受多个 Promise 并并行处理它们。
    • packageById() 被首先调用。当返回的承诺解决时,它会将data 传递给紧随其后的then()insightParams.map(metric => cloudWatch.getMetricStatisticsAsync(metric))insightParams 数组映射到一个Promises 数组,然后由Promise.all() 聚合,即当数组中的所有Promises 都解决后,Promise.all() 将一个结果数组传递给下一个.then() in它们显示为metricResults的链。
    猜你喜欢
    • 2014-12-16
    • 1970-01-01
    • 1970-01-01
    • 2013-03-21
    • 2020-07-30
    • 2012-05-04
    • 1970-01-01
    • 2020-01-01
    • 1970-01-01
    相关资源
    最近更新 更多