重构 KnexJS 多重承诺

Refactor KnexJS Multiple Promises

我 运行 多个 select 查询使用 Knexjs 和承诺。在我发送结果之前,我需要所有查询 return 一个值,我已经能够实现。但是我认为代码不是很优化。

knex(table).select('CRMContactId').where('FCIRecognition', '<', -49.00)
            .then(function(results) {

                data.largeclawbacks = results.length;

                knex(table).select('CRMContactId').where('PlanStatus', 'Out of Force').andWhere(function(){this.whereNot('IncomeType', 'Fund Based Commission').andWhereNot('IncomeType', 'Renewal Commission')})
                    .then(function(results) {

                        data.outofforce = results.length;

                        knex(table).select('CRMContactId').where('GroupOneCaption', 'Tier 2').andWhereNot('Payaways Made/Received', 'Payaway Made')
                            .andWhere((builder) => builder.whereIn('Plantype', ['Flexible Benefits','General Insurance','Group Critical Illness','Group Death In Service','Group Dental Insurance','Group Healthcare Cash Plan','Group Income Protection','Group Life','Group Life;Group Income Protection','Group PMI','Group Travel Insurance']))
                            .andWhereNot('Payable', 0)
                            .then(function(results) {

                                data.tier2 = results.length;

                                knex(table).select('CRMContactId').where((builder) => builder.where('GroupOneCaption', 'Tier 3').orWhere('GroupOneCaption', 'Tier 4')).
                                andWhereNot('Payaways Made/Received', 'Payaway Made')
                                    .andWhere((builder) => builder.whereIn('Plantype', ['Accident Sickness & Unemployment Insurance','AVC','Discretionary Managed Service','Endowment','Enhanced Pension Annuity','Executive Pension Plan','FSAVC','General Investment Account','Income Drawdown','Income Protection','Individual Retirement Account', 'Insurance / Investment Bond','Investment Trust','ISA','Long Term Care','Maximum Investment Plan','Money Purchase Contracted','OEIC / Unit Trust','Offshore Bond','Pension Annuity','Pension Term Assurance','Personal Equity Plan','Personal Pension Plan','Regular Savings Plan','Relevant Life Policy','s226 RAC','s32 Buyout Bond','Savings Account','SIPP','SSAS','Stakeholder Individual','Term Protection','Venture Capital Trust','Whole Of Life','Wrap']))
                                    .andWhereNot('Payable', 0)
                                    .then(function(results) {

                                        data.tier3 = results.length;

                                        knex(table).select('CRMContactId').where('FCIRecognition', '>', 500.00).andWhere('IncomeType', 'Renewal Commission')
                                            .then(function(results) {

                                                data.largerenewal = results.length;

                                                knex.raw(`SELECT ContactName AS Adviser, FORMAT(SUM(Payable),2) AS 'Renewal Income' FROM fci_test WHERE IncomeType IN ("Renewal Commission","Fund Based Commission","Ongoing Fee") AND \`Payaways Made/Received\` != 'Payaway Made' GROUP BY ContactName`)
                                                    .then(function(results){

                                                        data.renewalincome = results[0];
                                                        res.send(data)
                                                    })

                                            })
                                    })
                            })
                    })
            })

我确信有更好的编码方法并获得相同的结果。

我会首先关注可读性,然后才是性能。 通过首先使代码更具可读性,可以更容易地看到可以应用哪些优化。

经过一些重构后,我们可以得到类似于以下的代码:

knex(table).select('CRMContactId')
  .where('FCIRecognition', '<', -49.00)
  .then(function(results) {

    data.largeclawbacks = results.length;

    knex(table).select('CRMContactId')
      .where('PlanStatus', 'Out of Force')
      .andWhere((builder) => {
        builder.whereNot('IncomeType', 'Fund Based Commission')
          .andWhereNot('IncomeType', 'Renewal Commission');
      })
      .then(function(results) {

        data.outofforce = results.length;

        knex(table).select('CRMContactId')
          .where('GroupOneCaption', 'Tier 2')
          .andWhereNot('Payaways Made/Received', 'Payaway Made')
          .whereIn('Plantype', tier2PlanTypes)
          .andWhereNot('Payable', 0)
          .then(function(results) {

            data.tier2 = results.length;

            knex(table).select('CRMContactId')
              .whereIn('GroupOneCaption', ['Tier 3', 'Tier 4'])
              .andWhereNot('Payaways Made/Received', 'Payaway Made')
              .whereIn('Plantype', tier3PlanTypes)
              .andWhereNot('Payable', 0)
              .then(function(results) {

                data.tier3 = results.length;

                knex(table).select('CRMContactId')
                  .where('FCIRecognition', '>', 500.00)
                  .andWhere('IncomeType', 'Renewal Commission')
                  .then(function(results) {

                    data.largerenewal = results.length;

                    knex.raw(`SELECT ContactName AS Adviser, FORMAT(SUM(Payable),2) AS 'Renewal Income' FROM fci_test 
                              WHERE IncomeType IN ("Renewal Commission","Fund Based Commission","Ongoing Fee") 
                                AND \`Payaways Made/Received\` != 'Payaway Made' GROUP BY ContactName`)
                      .then(function(results){
                          data.renewalincome = results[0];
                          res.send(data)
                      });
                  })
              })
          })
      })
  });

看起来不多,但我看得更清楚所有查询都是相互独立的(我会用这个来优化)

之后,进一步重构我将每个查询保存在一个常量中,然后使用 Promise.all 一次发出所有查询以及它们完成以发送响应的方式。

const largeclawbacksQuery = knex(table).select('CRMContactId')
  .where('FCIRecognition', '<', -49.00);

const outofforceQuery = knex(table).select('CRMContactId')
  .where('PlanStatus', 'Out of Force')
  .andWhere((builder) => {
    builder.whereNot('IncomeType', 'Fund Based Commission')
      .andWhereNot('IncomeType', 'Renewal Commission')
  });

const tier2Query = knex(table).select('CRMContactId')
    .where('GroupOneCaption', 'Tier 2')
    .andWhereNot('Payaways Made/Received', 'Payaway Made')
    .whereIn('Plantype', tier2PlanTypes)
    .andWhereNot('Payable', 0);

const tier3Query = knex(table).select('CRMContactId')
  .whereIn('GroupOneCaption', ['Tier 3', 'Tier 4'])
  .andWhereNot('Payaways Made/Received', 'Payaway Made')
  .whereIn('Plantype', tier3PlanTypes)
  .andWhereNot('Payable', 0);

const largerenewalQuery = knex(table).select('CRMContactId')
  .where('FCIRecognition', '>', 500.00)
  .andWhere('IncomeType', 'Renewal Commission');

const renewalincomeQuery = knex.raw(
  `SELECT ContactName AS Adviser, FORMAT(SUM(Payable),2) AS 'Renewal Income' FROM fci_test 
    WHERE IncomeType IN ("Renewal Commission","Fund Based Commission","Ongoing Fee") 
      AND \`Payaways Made/Received\` != 'Payaway Made' GROUP BY ContactName`
);

Promise.all([largeclawbacksQuery, outofforceQuery, tier2Query, tier3Query, largerenewalQuery, renewalincomeQuery])
  .then((result) => {
    res.send({
      largeclawbacks: result[0].length,
      outofforce: result[1].length,
      tier2: results[2].length,
      tier3: results[3].length,
      largerenewal: results[4].length,
      renewalincome: results[4][0],
    });
  });

要点:

  • whereIn 可以链接起来,它们将转换为 sql 为 WHERE afield IN avalues AND bfield IN bvalues
  • 行长可以提高可读性,进而使代码更易于阅读
  • 如果我们将其视为承诺,我们可以等待查询构建器完成其查询

进一步改进:

  • 每个方法(whereIn、where、orWhere 等)returns 一个查询生成器 如 here 所述,可以通过克隆查询构建器实例来重用部分查询。这可以帮助您为 tier2Querytier3Query.
  • 定义基本查询
  • 我们可以等待 promise 使用更好的 API 解决,使用 promise-all-properties
  • 您可以直接请求 COUNT 而不是查询所有记录来获取长度值,这将提高性能。

只是盲目组织你的承诺并在链中正确返回它们看起来像这样:

knex(table)
  .select('CRMContactId')
  .where('FCIRecognition', '<', -49.0)
  .then(function(results) {
    data.largeclawbacks = results.length;

    return knex(table)
      .select('CRMContactId')
      .where('PlanStatus', 'Out of Force')
      .andWhere(function() {
        this.whereNot('IncomeType', 'Fund Based Commission').andWhereNot('IncomeType', 'Renewal Commission');
      });
  })
  .then(function(results) {
    data.outofforce = results.length;

    return knex(table)
      .select('CRMContactId')
      .where('GroupOneCaption', 'Tier 2')
      .andWhereNot('Payaways Made/Received', 'Payaway Made')
      .andWhere(builder =>
        builder.whereIn('Plantype', [
          'Flexible Benefits',
          'General Insurance',
          'Group Critical Illness',
          'Group Death In Service',
          'Group Dental Insurance',
          'Group Healthcare Cash Plan',
          'Group Income Protection',
          'Group Life',
          'Group Life;Group Income Protection',
          'Group PMI',
          'Group Travel Insurance'
        ])
      )
      .andWhereNot('Payable', 0);
  })
  .then(function(results) {
    data.tier2 = results.length;

    return knex(table)
      .select('CRMContactId')
      .where(builder => builder.where('GroupOneCaption', 'Tier 3').orWhere('GroupOneCaption', 'Tier 4'))
      .andWhereNot('Payaways Made/Received', 'Payaway Made')
      .andWhere(builder =>
        builder.whereIn('Plantype', [
          'Accident Sickness & Unemployment Insurance',
          'AVC',
          'Discretionary Managed Service',
          'Endowment',
          'Enhanced Pension Annuity',
          'Executive Pension Plan',
          'FSAVC',
          'General Investment Account',
          'Income Drawdown',
          'Income Protection',
          'Individual Retirement Account',
          'Insurance / Investment Bond',
          'Investment Trust',
          'ISA',
          'Long Term Care',
          'Maximum Investment Plan',
          'Money Purchase Contracted',
          'OEIC / Unit Trust',
          'Offshore Bond',
          'Pension Annuity',
          'Pension Term Assurance',
          'Personal Equity Plan',
          'Personal Pension Plan',
          'Regular Savings Plan',
          'Relevant Life Policy',
          's226 RAC',
          's32 Buyout Bond',
          'Savings Account',
          'SIPP',
          'SSAS',
          'Stakeholder Individual',
          'Term Protection',
          'Venture Capital Trust',
          'Whole Of Life',
          'Wrap'
        ])
      )
      .andWhereNot('Payable', 0);
  })
  .then(function(results) {
    data.tier3 = results.length;

    return knex(table)
      .select('CRMContactId')
      .where('FCIRecognition', '>', 500.0)
      .andWhere('IncomeType', 'Renewal Commission');
  })
  .then(function(results) {
    data.largerenewal = results.length;

    return knex.raw(
      `SELECT ContactName AS Adviser, FORMAT(SUM(Payable),2) AS 'Renewal Income' FROM fci_test WHERE IncomeType IN ("Renewal Commission","Fund Based Commission","Ongoing Fee") AND \`Payaways Made/Received\` != 'Payaway Made' GROUP BY ContactName`
    );
  })
  .then(function(results) {
    data.renewalincome = results[0];
    res.send(data);
  })
  .catch(err => {
    // TODO: send error response
  });

但是,根据您的编码风格,如果您使用 async / await 来防止需要收集结果的全局 data 对象,您将受益匪浅。