1874
1874
FE Engineer
桌游布道者
有粤语歌就不会有世界末日

关于接口的请求的一些思考

1874 - 2021-08-14 - 学习思考 / 并发
2021-8-14|最后更新: 2023-4-10|
type
Post
status
Published
date
Aug 14, 2021
slug
epmi4g
summary
category
学习思考
tags
并发
创建时间
Apr 7, 2023 07:15 PM
更新时间
Apr 10, 2023 05:46 AM
password
icon
Task List

前言

在优化小程序发布平台的时候,当时研究代码的时候,发现同事写的一段代码,是用来做异步任务切割的,当时很好奇这个任务切割可以解决什么问题。

调研

由于微信那边的限制,一次性只能同时发起 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() }
notion image

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() }
notion image

分组调用,等待上一组接口全部返回再调用下一组

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)
notion image

连接程池调用,只要有连接调用结束释放连接,便开始新的连接

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);
notion image

思考

可以看出,无论是先循环中一次性跑完异步请求,还是 await 串行去跑接口,或者说分组用 promise.all 去执行,或者用线程池去执行,100 个接口的用时都几乎没有差别。那就有了新的疑问,既然还是一个个处理的,为什么超过 6 个,微信就给限制了,反正也是一个个处理的。
经过分析,其实这 100 个接口虽然是同时发起,也就是并发的,但是却不是同时执行(并行)的,微信既限制了并发量,也限制了并行量,所以才导致了这样的结果。
当然,微信去限制并发和并行是为了安全考虑的,一次性发送太多接口,会导致系统炸了!
跨域场景汇总代码的马斯洛金字塔