async-pool 源码学习

1,854 阅读2分钟

ES7版

git地址:async-pool

使用

先用nodejs搞个接口

const http = require('http')
const Url = require('url')

const server = http.createServer((request, response) => {

    const { url } = request
    const parameter = Url.parse(url, true).query

    const count = Number(parameter.count)

    response.setHeader('Access-Control-Allow-Origin', '*')
    response.setHeader('Content-type', 'application/json')

    setTimeout(() => {
        response.end(parameter.count)
    }, count * 1000)

})

server.listen(3000, () => console.log('服务已开启!'))

客户端使用

偷懒用一下 axios (也没偷上, 找 CDN 还得找一会, 不如 XMLHttpRequest 写了)

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

<script>
    function api(count) {
        return axios.get('http://192.168.5.8:3000/', {
            params: { count }
        })
    }

    asyncPool(2, [1, 1, 1, 1], api)
</script>

源码分析

/*
    poolLimit: 可同时发起请求的最大数量
    array: 将依次作为 iteratorFn 的参数执行
    iteratorFn 一个函数 需要返回 Promise
*/
async function asyncPool(poolLimit, array, iteratorFn) {
    // 最终传给 Promise.all 
    const ret = [] 
    // 正在执行的任务 -就是还没有被解决的 Promise 数组, 这里的 Promise 处于 pending 状态 -限制最大数量就靠它了
    const executing = []
    
    // ES6 迭代 -数组可被 `for of` 迭代, 对象不可以
    for (const item of array) { 
        // 每次迭代 都向结果 push 新的 Promise  至于为什么又套了一层 Promise.resolve(), 是为了防止使用的时候没有传入 Promise 导致后续代码执行错误
        const p = Promise.resolve().then(() => iteratorFn(item, array))
        ret.push(p)

        // 限制并发的逻辑
        if (poolLimit <= array.length) {
            /* 
                在 then 的回调函数中, 当这个 Promise 被解决后, 由 pending 变为 fulfilled 
                已经拿到了服务端数据, 就删除该 Promise,腾出地方添加下一个 Promise,始终保持 executing 内有两个元素
                then 的回调是异步, 别认为 `splice` 在 `executing.push(e)` 的上边很奇怪 
                
                // 感觉这样写也可以呢,为什么要又定义一个变量 e 呢
                p.then(() => executing.splice(executing.indexOf(p), 1))
                executing.push(p)
            */
            const e = p.then(() => executing.splice(executing.indexOf(e), 1))
            executing.push(e)

            /*
                当正在执行的任务到达限制数量的时候, 利用 await 等待执行
                Promise.race 的作用: 假如 poolLimit 是 2, executing 的任务有任意一个被解决, 
                Promise.race 就是 fulfilled 状态, 之后进入第 3 次 for 循环
            */
            if (executing.length >= poolLimit) {
                await Promise.race(executing)
            }
        }
    }
    // 最后按顺序返回结果
    return Promise.all(ret)
}

疑惑

  • 疑惑一

    const p = Promise.resolve().then(() => iteratorFn(item, array))
    

    没有理解源码中这句为什么要套一层 Promise.resolve()

    测试改成下面也可以呀

    const p = iteratorFn(item, array)
    
  • 疑惑二

    对于这两句,为什么又定义了一个变量 e 呢?

    const e = p.then(() => executing.splice(executing.indexOf(e), 1)) 
    executing.push(e)
    

    这块代码的目的就是在获取到结果后删除该 Promise 留出地方好添加下一个Promise呀

    测试改成下面也可以呀

    p.then(() => executing.splice(executing.indexOf(p), 1))
    executing.push(p)