本文最后更新于:2020年12月25日 上午
场景
之前写了个 user.js
脚本来抓取百度网盘的文件元信息列表,用来进行二级查看和分析,脚本放到了 GreasyFork。最开始为了简化代码直接使用了 async/await
单线程进行异步请求,导致请求的速度十分不理想!
关键代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
|
class File {
constructor(path, parent, name, size, isdir, origin) { this.path = path this.parent = parent this.name = name this.size = size this.isdir = isdir this.orgin = origin } }
async function getDir(path) { var baseUrl = 'https://pan.baidu.com/api/list?' try { var res = await fetch(`${baseUrl}dir=${encodeURIComponent(path)}`) var json = await res.json() return json.list } catch (err) { console.log(`读取文件夹 ${path} 发生了错误:`, err) return [] } }
async function asyncFlatMap(arr, fn) { var res = [] for (const i in arr) { res.push(...(await fn(arr[i]))) } return res }
async function syncList(path) { var fileList = await getDir(path) return asyncFlatMap(fileList, async (file) => { var res = new File( file.path, path, file.server_filename, file.size, file.isdir, file, ) if (res.isdir !== 1) { return [res] } return [res].concat(await syncList(res.path)) }) }
|
可以看到,使用的方式是 递归 + 单异步
,这就导致了脚本的效率不高,使用体验很差!
解决
吾辈想要使用多异步模式,需要解决的问题有二:
- 如何知道现在有多个异步在执行并且在数量过多时等待
- 如何知道所有的请求都执行完成了然后结束
解决思路
- 判断并限定异步的数量
- 添加记录正在执行的异步请求的计数器
execQueue
- 每次请求前先检查
execQueue
是否到达限定值
- 如果没有,
execQueue + 1
- 如果有,等待
execQueue
减小
- 执行请求,请求结束
execQueue - 1
- 判断所有请求都执行完成
- 添加记录正在等待的异步请求的计数器
waitQueue
- 在判断
execQueue
是否到达限定值之前 waitQueue + 1
- 在判断
execQueue
是否到达限定值之后(等待之后) waitQueue - 1
- 请求结束后判断
waitQueue
和 waitQueue
是否均为 0
具体实现如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
|
class File {
constructor(path, parent, name, size, isdir, origin) { this.path = path this.parent = parent this.name = name this.size = size this.isdir = isdir this.orgin = origin } }
function wait(param) { return new Promise((resolve) => { if (typeof param === 'number') { setTimeout(resolve, param) } else if (typeof param === 'function') { var timer = setInterval(() => { if (param()) { clearInterval(timer) resolve() } }, 100) } else { resolve() } }) }
async function getDir(path) { var baseUrl = 'https://pan.baidu.com/api/list?' try { var res = await fetch(`${baseUrl}dir=${encodeURIComponent(path)}`) var json = await res.json() return json.list } catch (err) { console.log(`读取文件夹 ${path} 发生了错误:`, err) return [] } }
async function asyncList(path = '/', limit = 5) { return new Promise((resolve) => { var count = 1 var execCount = 0 var waitQueue = 0
var result = [] async function children(path) { waitQueue++ await wait(() => execCount < limit) waitQueue-- execCount++ getDir(path).then((fileList) => { fileList.forEach((file) => { var res = new File( file.path, path, file.server_filename, file.size, file.isdir, file, ) result.push(res) if (res.isdir === 1) { children(res.path) } }) if (--execCount === 0 && waitQueue === 0) { resolve(result) } }) } children(path) }) }
|
吾辈使用 timing
函数测试了一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
function timing(fn) { var begin = performance.now() var result = fn() if (!(result instanceof Promise)) { return performance.now() - begin } return result.then(() => performance.now() - begin) }
|
请求了 2028
次,两个函数的性能比较如下(单位是毫秒)
asyncList
:109858.80000004545
syncList
:451904.3000000529
差距近 4.5 倍,几乎等同于默认的异步倍数了,看起来优化还是很值得呢!
附:其实 asyncList
如果使用单异步的话效率反而更低,因为要做一些额外的判断导致单次请求更慢,但因为多个异步请求同时执行的缘故因此缺点被弥补了
那么,关于 JavaScript 使用递归异步的请求就到这里啦