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
结果分析
- 为什么执行时间不同?
其实从 async 标记放置的位置就不难看出为什么了
test3的async标记在forEach的内联函数上,而不是包住整段代码的test3函数上
因此在test3中,forEach内联函数内await才生效,对于forEach外的代码而言,并不知道forEach执行了异步操作。
即,对外部而言,就好比执行了
ids.forEach((id)=>{
console.log(id);
});
- 为什么最终的结果是有序的?
是因为是顺序遍历的吗?
确实有那么点关系,因为我定义死了精准的500ms延迟
在真实的select中,查询实际必然是波动的,不可能保证有序了
模拟波动
delay用随机函数生产,范围在400~600ms之间
delay = 500 + 100 + (-200)*Math.random();
此时,test3的结果成功乱序。
因为test1和test2的查询结果是Promise.all根据Promise链的顺序整理好的结果
而test3是我们手动在每次结束时丢进的result队列,顺序与查询完成的顺序一致
所以,除非需要保证结果队列的顺序与完成的顺序一致,一般不建议test3的方案
况且,test3的实现比较麻烦,不仅多个判定,还要多套一层Promise…