从异步函数获取模块导出

时间:2019-01-11 10:53:03

标签: javascript node.js oracle

在async.js模块中,我在asyncTasks数组中存储了三个SQL查询。在async.parallel函数中,结果通过console.log(results)发布,一切看起来都很好。但是如何将结果通过var as = async.getExtradata();从该模块调用的地方传回main.js。我认为问题是,当我想在async.js中返回响应时,async.parallel函数尚未完成。我该如何处理?

// async.js:
var response = [];

function getExradata(reqq, ress){
    oracledb.getConnection(
        config,
        function (err, connection) {
            if (err) { console.error(err.message); return; }
            var asyncTasks = [];
            var items = ['1234','3215','2306'];

            items.forEach(function(item){
                asyncTasks.push(function(callback){
                    connection.execute(
                        "SELECT * FROM mde.mobile_blgkopf WHERE blg_id = 
                         '"+item+"'",
                        [],
                        {
                            outFormat: oracledb.OBJECT
                        },
                        function (err, result) {
                            if (err) {                                   
                                return;
                            }
                            callback(null, result.rows);
                        });
                });
            });
            async.parallel(asyncTasks,
                function(err, results) {
                    console.log(results);
                    response = results;                       
                });
        });
    return response;
};
module.exports.getExtradata = getExradata;


// main.js:
var async = require(__dirname + '/async.js');
var as = async.getExtradata();

4 个答案:

答案 0 :(得分:0)

请记住,一次连接只能做一件事。因此,即使您使用的是async.parallel,查询仍将一次执行一次(您的代码混淆了这一事实)。更糟糕的是,您可能会使用比预期更多的后台线程(在等待前面的线程完成时,基本上将其锁定)。这将阻止您的应用程序扩展,并导致难以跟踪错误。请勿以这种方式将async.parallel与node-oracledb一起使用。

此外,您还将字符串与查询中的值一起使用。这可能会使您面临SQL注入和性能问题的困扰。确保改用绑定变量。 https://oracle.github.io/node-oracledb/doc/api.html#-18-bind-parameters-for-prepared-statements

在性能方面,往返和数据传输通常是最大的问题。尽管这里无法避免数据传输,但是您可以一次往返获取数据。

这是一个使用异步/等待,绑定变量和单次往返的示例。

const oracledb = require('oracledb');
const config = require('./dbConfig.js');

async function runTest() {
  let conn;

  try {
    // These values would come from the user
    const group1Id = 30;
    const group2Id = 50;
    const group3Id = 80;

    conn = await oracledb.getConnection(config);

    // First get the data using a single round trip and bind variables.
    const result = await conn.execute(
     `select * 
      from employees 
      where department_id in (:group1, :group2, :group3)`,
      {
        group1: group1Id,
        group2: group2Id,
        group3: group3Id
      },
      {
        outFormat: oracledb.OBJECT
      }
    );

    // Now that we have ALL the data, we can split it up into the buckets
    // we need. There are lots of different ways you could do this, this
    // is just one example.
    const empsInGroup1 = [];
    const empsInGroup2 = [];
    const empsInGroup3 = [];

    for (let i = 0; i < result.rows.length; i += 1) {
      switch (result.rows[i].DEPARTMENT_ID) {
        case group1Id:
          empsInGroup1.push(result.rows[i]);
          break;
        case group2Id:
          empsInGroup2.push(result.rows[i]);
          break;
        case group3Id:
          empsInGroup3.push(result.rows[i]);
          break;
      }
    }

    console.log(empsInGroup1.length); // 6
    console.log(empsInGroup2.length); // 45
    console.log(empsInGroup3.length); // 34
  } catch (err) {
    console.error(err);
  } finally {
    if (conn) {
      try {
        await conn.close();
      } catch (err) {
        console.error(err);
      }
    }
  }
}

runTest();

当然,那不是您真正要的。 :)要了解有关HTTP API,数据库逻辑和各种其他模块的异步计时的更多信息,请参阅关于创建REST API的本系列文章:https://jsao.io/2018/03/creating-a-rest-api-with-node-js-and-oracle-database/

答案 1 :(得分:0)

感谢您的出色回答DanMcGhan,您为我提供了很多帮助。我在Oracle / SQL中是一个新手,实际上我正在调用公司提供的PL / SQL过程,而不仅仅是一个简单的SELECT语句。我觉得很难满足我的需求。不知道是否应该为此打开新线程,但是我认为上下文很重要。给我值的过程是这样的:

BEGIN

    SELECT Nvl(Sum(loat_mg),0)
      INTO v_best_lag
      FROM unitrade.loa
    WHERE loa_at_at_se_se_ag_ag_wg = v_ber
       AND loa_at_at_se_se_ag_ag_no = v_wgr
       AND loa_at_at_se_se_no       = v_agr
       AND loa_at_at_no             = v_art
       AND loat_zen = v_fil;

    SELECT Sum(LOAT_MG * stzu_mg)
      INTO v_best_lagkombi
      from unitrade.stz
      join unitrade.loa
        on LOA_AT_AT_SE_SE_AG_AG_WG = stz_st_at_se_se_ag_ag_wg
       AND LOA_AT_AT_SE_SE_AG_AG_NO = stz_st_at_se_se_ag_ag_no
       AND LOA_AT_AT_SE_SE_NO       = stz_st_at_se_se_no
       AND LOA_AT_AT_NO             = stz_st_at_no
    WHERE stz_at_at_se_se_ag_ag_wg = v_ber
       AND stz_at_at_se_se_ag_ag_no = v_wgr
       AND stz_at_at_se_se_no       = v_agr
       AND stz_at_at_no             = v_art
       AND LOAT_ZEN                 = v_fil;

    SELECT Nvl(Sum(bstd_resmg),0)
      INTO v_off_aek
      FROM bst_bsd_view
    WHERE bsd_at_at_se_se_ag_ag_wg     = v_ber
       AND bsd_at_at_se_se_ag_ag_no     = v_wgr
       AND bsd_at_at_se_se_no     = v_agr
       AND bsd_at_at_no     = v_art
       AND bstd_zen = v_fil;

END;
如您所见,

该过程返回一个项目的值,并且该项目的ID由五个值(ber,wgr,agr,art,fil)组成。那么,如何更改此过程,使每个项目只有一行,而值却为列? PS:我在您的网站(jsao.io)上阅读了很多文章,这对我的工作起了很大的帮助。但是现在我被困住了

答案 2 :(得分:0)

TLDR该驱动程序当前不适用于复杂的数据结构(用户定义的类型和PL / SQL记录),但可以与简单的数组(数字,varchar2和日期)一起使用。在您的情况下,您可以发送需要相关数据的ID数组,并填充数据绑定。 id数组是in / out的,因为不能保证数组的顺序将与发送的数组匹配(除非您自己这样做)。

我使用的是包而不是独立的过程,所以我可以声明数组类型。

给出以下软件包:

create or replace package my_pkg
as

type number_aat is table of number
  index by pls_integer;

procedure get_vals(
  p_dept_ids     in out number_aat,
  p_emp_counts   out number_aat,
  p_sum_salaries out number_aat
);

end my_pkg;
/

create or replace package body my_pkg
as

procedure get_vals(
  p_dept_ids     in out number_aat,
  p_emp_counts   out number_aat,
  p_sum_salaries out number_aat
)

is

  -- Use an existing varray type so you do not have to create one
  l_number_list sys.odcinumberlist := sys.odcinumberlist();

begin

  -- Extend the varray to match the number of elements in p_dept_ids
  l_number_list.extend(p_dept_ids.count);

  -- Populate the varray with the values from p_dept_ids
  for x in 1 .. p_dept_ids.count
  loop
    l_number_list(x) := p_dept_ids(x);
  end loop;

  -- Populate the out binds (associative arrays) with the data
  select department_id, 
    count(*), 
    sum(salary)
  bulk collect into p_dept_ids, 
    p_emp_counts, 
    p_sum_salaries
  from employees
  where department_id in (
    select column_value
    from table(l_number_list)
  )
  group by department_id;

end get_vals;

end my_pkg;
/

以下方法应该起作用:

const oracledb = require('oracledb');
const config = require('./dbConfig.js');

async function runTest() {
  let conn;

  try {
    const deptIdsFromEndUser = [20, 30, 40, 50, 60, 70, 80];
    conn = await oracledb.getConnection(config);

    const result = await conn.execute(
     `begin

        my_pkg.get_vals(:dept_ids, :emp_counts, :sum_salaries);

      end;`,
      {
        dept_ids: {
          dir: oracledb.BIND_INOUT,
          type: oracledb.NUMBER,
          val: deptIdsFromEndUser,
          maxArraySize: 100
        },
        emp_counts: {
          dir: oracledb.BIND_OUT,
          type: oracledb.NUMBER,
          maxArraySize: 100
        },
        sum_salaries: {
          dir: oracledb.BIND_OUT,
          type: oracledb.NUMBER,
          maxArraySize: 100
        }
      }
    );

    // Now that we have the values in JS we can do what we need to with them.
    console.log(result.outBinds);

    // { dept_ids: [ 20, 30, 40, 50, 60, 70, 80 ],
    //   emp_counts: [ 2, 6, 1, 45, 5, 1, 34 ],
    //   sum_salaries: [ 19000, 24900, 6500, 156400, 28800, 10000, 304500 ] }
  } catch (err) {
    console.error(err);
  } finally {
    if (conn) {
      try {
        await conn.close();
      } catch (err) {
        console.error(err);
      }
    }
  }
}

runTest();

答案 3 :(得分:0)

这是一个新答案,我希望与您的用例更紧密地吻合。我仍在尝试让您按组工作,而不是逐行讨论情况。

给出以下对象(您可以运行这些对象来重新创建对象):

-- Create a parent table and populate it.
create table t1 (
  id1 number not null,
  id2 number not null,
  id3 number not null,
  id4 number not null,
  val1 number,
  constraint t_u1 unique (id1, id2, id3, id4)
);

insert into t1 (id1, id2, id3, id4, val1) values (1, 1, 1, 1, 1);
insert into t1 (id1, id2, id3, id4, val1) values (1, 1, 1, 2, 2);
insert into t1 (id1, id2, id3, id4, val1) values (1, 1, 2, 1, 3);
insert into t1 (id1, id2, id3, id4, val1) values (1, 1, 2, 2, 4);
insert into t1 (id1, id2, id3, id4, val1) values (1, 2, 1, 1, 5);
insert into t1 (id1, id2, id3, id4, val1) values (1, 2, 1, 2, 6);
insert into t1 (id1, id2, id3, id4, val1) values (1, 2, 2, 1, 7);
insert into t1 (id1, id2, id3, id4, val1) values (1, 2, 2, 2, 8);
insert into t1 (id1, id2, id3, id4, val1) values (2, 1, 1, 1, 9);
insert into t1 (id1, id2, id3, id4, val1) values (2, 1, 1, 2, 10);
insert into t1 (id1, id2, id3, id4, val1) values (2, 1, 2, 1, 11);
insert into t1 (id1, id2, id3, id4, val1) values (2, 1, 2, 2, 12);
insert into t1 (id1, id2, id3, id4, val1) values (2, 2, 1, 1, 13);
insert into t1 (id1, id2, id3, id4, val1) values (2, 2, 1, 2, 14);
insert into t1 (id1, id2, id3, id4, val1) values (2, 2, 2, 1, 15);
insert into t1 (id1, id2, id3, id4, val1) values (2, 2, 2, 2, 16);

-- Create a child table and populate it.
create table t2 (
  id1 number not null,
  id2 number not null,
  id3 number not null,
  id4 number not null,
  val1 number(,0),
  val2 number,
  foreign key (id1, id2, id3, id4)
    references t1 (id1, id2, id3, id4)
);

insert into t2
select round(dbms_random.value(1,2)),
  round(dbms_random.value(1,2)),
  round(dbms_random.value(1,2)),
  round(dbms_random.value(1,2)),
  round(dbms_random.value(1,100)),
  round(dbms_random.value(1,100))
from dual
connect by rownum <= 1000;

-- Delete some data from the child table so that not every
-- primary key combination has a match.
delete from t2
where id1 = 1
  and id2 = 2
  and id3 = 2
  and id4 = 1;

delete from t2
where id1 = 2
  and id2 = 2
  and id3 = 2
  and id4 = 1;

-- Create a temp table to demonstrate another way to get
-- the values in SQL.
create global temporary table id_values(
  id1 number,
  id2 number,
  id3 number,
  id4 number
) on commit delete rows;

create or replace package my_pkg
as

type number_aat is table of number
  index by pls_integer;

type varchar2_aat is table of varchar2(256)
  index by pls_integer;

procedure get_vals(
  p_id1_vals    in out number_aat,
  p_id2_vals    in out number_aat,
  p_id3_vals    in out number_aat,
  p_id4_vals    in out number_aat,
  p_parent_uks  out varchar2_aat,
  p_parent_sums out number_aat,
  p_child_uks   out varchar2_aat,
  p_child_sums  out number_aat,
  p_child_avgs  out number_aat
);

end my_pkg;
/

create or replace package body my_pkg
as

procedure get_vals(
  p_id1_vals    in out number_aat,
  p_id2_vals    in out number_aat,
  p_id3_vals    in out number_aat,
  p_id4_vals    in out number_aat,
  p_parent_uks  out varchar2_aat,
  p_parent_sums out number_aat,
  p_child_uks   out varchar2_aat,
  p_child_sums  out number_aat,
  p_child_avgs  out number_aat
)

is

begin

  if not p_id1_vals.count = p_id2_vals.count
    and p_id1_vals.count = p_id3_vals.count
    and p_id1_vals.count = p_id4_vals.count
  then
    raise_application_error(-20001, 'ID arrays must have the same number of elements');
  end if;

  -- Move the ids from the arrays passed into the temporary table
  -- so they can be accessed via SQL
  forall idx in 1 .. p_id1_vals.count
    insert into id_values (id1, id2, id3, id4)
      values (p_id1_vals(idx), p_id2_vals(idx), p_id3_vals(idx), p_id4_vals(idx));

  select id1 || ':' || id2 || ':'  || id3 || ':' || id4 as ids_as_string,
    sum(val1)
  bulk collect into p_parent_uks,
    p_parent_sums 
  from t1
  where (id1, id2, id3, id4) in (
    select id1, id2, id3, id4
    from id_values
  )
  group by id1, 
    id2,
    id3,
    id4;

  select id1 || ':' || id2 || ':'  || id3 || ':' || id4 as ids_as_string,
    sum(val1),
    round(avg(val2))
  bulk collect into p_child_uks,
    p_child_sums,
    p_child_avgs 
  from t2
  where (id1, id2, id3, id4) in (
    select id1, id2, id3, id4
    from id_values
  )
  group by id1,
    id2,
    id3,
    id4;

end get_vals;

end my_pkg;
/

以下内容应该起作用...

const oracledb = require('oracledb');
const config = require('./dbConfig.js');

async function runTest() {
  let conn;

  try {
    // These values would come from an end user
    const id1Vals = [1, 2, 1, 2, 2, 1, 2, 1];
    const id2Vals = [2, 1, 1, 1, 2, 2, 1, 1];
    const id3Vals = [2, 2, 2, 1, 2, 2, 2, 2];
    const id4Vals = [1, 1, 2, 2, 1, 1, 2, 1];

    conn = await oracledb.getConnection(config);

    const result = await conn.execute(
     `begin

        my_pkg.get_vals(
          :id1_vals, 
          :id2_vals, 
          :id3_vals, 
          :id4_vals, 
          :parent_uks, 
          :parent_sums, 
          :child_uks, 
          :child_sums, 
          :child_avgs
        );

      end;`,
      {
        id1_vals: {
          dir: oracledb.BIND_IN,
          type: oracledb.NUMBER,
          val: id1Vals,
          maxArraySize: 100
        },
        id2_vals: {
          dir: oracledb.BIND_IN,
          type: oracledb.NUMBER,
          val: id2Vals,
          maxArraySize: 100
        },
        id3_vals: {
          dir: oracledb.BIND_IN,
          type: oracledb.NUMBER,
          val: id3Vals,
          maxArraySize: 100
        },
        id4_vals: {
          dir: oracledb.BIND_IN,
          type: oracledb.NUMBER,
          val: id4Vals,
          maxArraySize: 100
        },
        parent_uks: {
          dir: oracledb.BIND_OUT,
          type: oracledb.STRING,
          maxArraySize: 100
        },
        parent_sums: {
          dir: oracledb.BIND_OUT,
          type: oracledb.NUMBER,
          maxArraySize: 100
        },
        child_uks: {
          dir: oracledb.BIND_OUT,
          type: oracledb.STRING,
          maxArraySize: 100
        },
        child_sums: {
          dir: oracledb.BIND_OUT,
          type: oracledb.NUMBER,
          maxArraySize: 100
        },
        child_avgs: {
          dir: oracledb.BIND_OUT,
          type: oracledb.NUMBER,
          maxArraySize: 100
        }
      }
    );

    // Now that we have the values in JS we can do what we need to with them.
    console.log(result.outBinds);

    // Create a new object to hold the reformatted results.
    const reformattedResults = {};

    // Ensure that there's on property for each primary key combination requested.
    for (let x = 0; x < id1Vals.length; x += 1) {
      let idAsString = `${id1Vals[x]}:${id2Vals[x]}:${id3Vals[x]}:${id4Vals[x]}`;
      reformattedResults[idAsString] = {};
    }

    // Populate the appropriate property with the correct values from the parent
    // table.
    for (let x = 0; x < result.outBinds.parent_uks.length; x += 1) {
      reformattedResults[result.outBinds.parent_uks[x]].parentSum = result.outBinds.parent_sums[x];
    }

    // Populate the appropriate property with the correct values from the child
    // table.
    for (let x = 0; x < result.outBinds.child_uks.length; x += 1) {
      reformattedResults[result.outBinds.child_uks[x]].childSum = result.outBinds.child_sums[x];
      reformattedResults[result.outBinds.child_uks[x]].childAvg = result.outBinds.child_avgs[x];
    }

    console.log(reformattedResults);
  } catch (err) {
    console.error(err);
  } finally {
    if (conn) {
      try {
        await conn.close();
      } catch (err) {
        console.error(err);
      }
    }
  }
}

runTest();

我创建了单独的“ id out绑定”,为我正在访问的每个表创建了一个“ id out绑定”,因为我可能不会总能获得成功。您的数据可能会有所不同,因此您可能会采用不同的方法(例如,加入事实表以确保存在行)。

和往常一样,您可以采用许多不同的方法。希望您开始看到一些对您有用的选项。