前言
在优化小程序发布平台的时候,当时研究代码的时候,发现同事写的一段代码,是用来做异步任务切割的,当时很好奇这个任务切割可以解决什么问题。
调研
由于微信那边的限制,一次性只能同时发起 6 个接口请求,如果发起的更多就会报错。所以才有了这个任务切割的代码,但是在我实际的测试过程中发现,同时发起的 6 个请求,其实还是串行执行的,上一个结束之后才会调用下一个,然后我就对比了各个方式的调用结果。
测试
一次性发送 100 个请求
const post = () => {
return new Promise((resolve, reject) => {
fetch("http://localhost:18740/users/1")
.then((res) => res.json())
.then((r) => {
console.log(r);
resolve(r);
});
});
};
for (let index = 0; index < 100; index++) {
post();
}
await 串行调用 100 个接口
const post = () => {
return new Promise((resolve, reject) => {
fetch("http://localhost:18740/users/1")
.then((res) => res.json())
.then((r) => {
console.log(r);
resolve(r);
});
});
};
for (let index = 0; index < 100; index++) {
await post();
}
分组调用,等待上一组接口全部返回再调用下一组
const flatJobs = (jobs, concurrent_count) => {
const concurrentCount = concurrent_count || 6;
return jobs.reduce(
(queues, c, i) => {
if (i % concurrentCount > 0) {
queues[queues.length - 1].push(c);
return queues;
}
queues.push([c]);
return queues;
},
[[]]
);
};
const runSerialJobsQueue = async (jobs) => {
let p = 0;
const res = [];
while (p < jobs.length) {
const part_res = await Promise.all(
jobs[p].map((fn) => fn().catch(() => false))
);
res.push(...part_res);
++p;
}
return res;
};
const post = () => {
return new Promise((resolve, reject) => {
fetch("http://localhost:18740/users/1")
.then((res) => res.json())
.then((r) => resolve(r));
});
};
const rows = Array(100).fill(0);
const jobs = rows.map((row) => {
return async () => {
const res = await post(row);
return { res };
};
});
const j = flatJobs(jobs);
runSerialJobsQueue(j);
连接程池调用,只要有连接调用结束释放连接,便开始新的连接
async function asyncPool(poolLimit, array, iteratorFn) {
// if (shouldAssert) {
// assertType(poolLimit, "poolLimit", ["number"]);
// assertType(array, "array", ["array"]);
// assertType(iteratorFn, "iteratorFn", ["function"]);
// }
const ret = [];
const executing = [];
for (const item of array) {
const p = Promise.resolve().then(() => iteratorFn(item, array));
ret.push(p);
if (poolLimit <= array.length) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= poolLimit) {
await Promise.race(executing);
}
}
}
return Promise.all(ret);
}
const post = () => {
return new Promise((resolve, reject) => {
fetch("http://localhost:18740/users/1")
.then((res) => res.json())
.then((r) => {
console.log(r);
resolve(r);
});
});
};
const results = asyncPool(6, Array(100).fill(0), post);
思考
可以看出,无论是先循环中一次性跑完异步请求,还是 await 串行去跑接口,或者说分组用 promise.all 去执行,或者用线程池去执行,100 个接口的用时都几乎没有差别。那就有了新的疑问,既然还是一个个处理的,为什么超过 6 个,微信就给限制了,反正也是一个个处理的。
经过分析,其实这 100 个接口虽然是同时发起,也就是并发的,但是却不是同时执行(并行)的,微信既限制了并发量,也限制了并行量,所以才导致了这样的结果。
当然,微信去限制并发和并行是为了安全考虑的,一次性发送太多接口,会导致系统炸了!