Promise应用 - Node并发查询

Promise应用 - Node并发查询

我们借助并发查询数据库的这一情景来感受Promise在并发中的应用,为了让查询过程更加透明,笔者简单模拟了数据库的select行为

模拟sql-select行为

selectById传入待查询的用户id,每次查询设置了一个500毫秒的delay,这代表半秒后才能拿到查询结果,写死了一个id从1递增到10的user数组作为数据库users表
selectByIdPromise将这一行为封装为一个返回Promise的方法

// 模拟sql-select行为
function selectById(id, callback) {
    
    
  // 延迟时间
  let delay = 500;  //单位ms
//   delay = 500 + 100 + (-200)*Math.random();
  setTimeout(() => {
    
    
    const users = [
      {
    
     id: 1, name: "user1" },
      {
    
     id: 2, name: "user2" },
      {
    
     id: 3, name: "user3" },
      {
    
     id: 4, name: "user4" },
      {
    
     id: 5, name: "user5" },
      {
    
     id: 6, name: "user6" },
      {
    
     id: 7, name: "user7" },
      {
    
     id: 8, name: "user8" },
      {
    
     id: 9, name: "user9" },
      {
    
     id: 10, name: "user10" },
    ];
    let user = users.find((user) => user.id == id);
    //console.log(delay);
    callback(user);
  }, delay);
}

// 将select异步操作包装为Promise
function selectByIdPromise(id) {
    
    
  return new Promise((rs, rj) => {
    
    
    selectById(id, (rsp, err) => {
    
    
      if (err) {
    
    
        rj(err);
      }
      rs(rsp);
    });
  });
}

待查询的id

准备好我们要并发查询的id

const ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

案例实践

案例1 Promise.all

function test1(ids) {
    
    
  // map形成查询的Promise链
  const selects = ids.map((id) => {
    
    
    return selectByIdPromise(id);
  });
  // 等待Promise链全部结束
  Promise.all(selects).then((results) => {
    
    
    console.log("--------test1--------")
    console.log(results);
  });
  // 预计0.6s左右完成
}

案例2 Promise.all变种

async function test2(ids) {
    
    
  let selects = [];
  // forEach形成Promise链
  ids.forEach((id) => {
    
    
    const result = selectByIdPromise(id);
    selects.push(result);
  });
  // Promise.all的返回也是一个Promise,因此可以用await接收
  const results = await Promise.all(selects);
  console.log("--------test2--------")
  console.log(results);
  // 预计0.6s左右完成
}

案例3 forEach

// 如果我们在forEach的回调函数中await等待select结果呢?
async function test3(ids) {
    
    
    function selectTest3(ids){
    
    
        return new Promise((rs,rj)=>{
    
    
            let results = [];
            ids.forEach(async (id) => {
    
    
              // 等0.5s左右拿到结果
              const result = await selectByIdPromise(id);
              results.push(result);
              if (results.length==ids.length) {
    
    
                rs(results);
              }
            });
        })
    }
    const results = await selectTest3(ids);
    console.log("--------test3--------")
    console.log(results);
    // 会在(0.5s * 10 + N(0~1)s)≈5s左右完成吗?
}

案例4 for-of

// 如果是for-of循环呢?
async function test4(ids) {
    
    
    let results = [];
    for (const id of ids) {
    
    
        const result = await selectByIdPromise(id);
        results.push(result);
    }
    console.log("--------test4--------")
    console.log(results);
}

案例5 for-in

// 如果是for-in循环呢?for-in等效for(let i;i<len;i++)
async function test5(ids) {
    
    
    let results = [];
    for (const idx in ids) {
    
    
        const result = await selectByIdPromise(ids[idx]);
        results.push(result);
    }
    console.log("--------test5--------")
    console.log(results);
}

执行测试

test1(ids); // 约0.6s
test2(ids); // 约0.6s
test3(ids); // 约0.6s
test4(ids); // 约5s
test5(ids); // 约5s

结果分析

  1. 为什么执行时间不同?

其实从 async 标记放置的位置就不难看出为什么了
test3的async标记在forEach的内联函数上,而不是包住整段代码的test3函数上
因此在test3中,forEach内联函数内await才生效,对于forEach外的代码而言,并不知道forEach执行了异步操作。
即,对外部而言,就好比执行了

ids.forEach((id)=>{
    
    
  console.log(id);
}); 
  1. 为什么最终的结果是有序的?

是因为是顺序遍历的吗?
确实有那么点关系,因为我定义死了精准的500ms延迟
在真实的select中,查询实际必然是波动的,不可能保证有序了

模拟波动

delay用随机函数生产,范围在400~600ms之间

delay = 500 + 100 + (-200)*Math.random();

此时,test3的结果成功乱序。
因为test1和test2的查询结果是Promise.all根据Promise链的顺序整理好的结果
而test3是我们手动在每次结束时丢进的result队列,顺序与查询完成的顺序一致
所以,除非需要保证结果队列的顺序与完成的顺序一致,一般不建议test3的方案
况且,test3的实现比较麻烦,不仅多个判定,还要多套一层Promise…

猜你喜欢

转载自blog.csdn.net/m0_51810668/article/details/130695978
今日推荐