我明白了,前端并发函数

简介: 谷歌浏览器的并发数是6个,有六个请求正在处理,那么其它的任务就会排队,等六个请求任务中的某一个完成之后,就会立马插入进去执行。这样一来,就清楚了。

前言

以前的工作中并没有涉及到这块,群里然叔老师出了一个如何实现一个并发函数的打卡题,以前想过前端并发有点懵,后来仔细想想其实浏览器的并发请求控制就已经实现了这个功能,我可以抽象分析一下,然后以模拟的方式去实现它。

谷歌浏览器的并发数是6个,有六个请求正在处理,那么其它的任务就会排队,等六个请求任务中的某一个完成之后,就会立马插入进去执行。这样一来,就清楚了。

设计

设计理念:控制六个任务(线程),结构化任务模型,递归的去执行任务,可以在执行完毕后直接打印结果,也可以在所有任务执行完毕,再打印结果。

参数配置:至少两个,比如任务列表、并发数。

初始化任务:需要将普通任务处理成指定规范格式的任务,不规范就不好控制了噢。

任务状态:等待await、运行中run、执行成功 success、执行失败bad。

代码我已经实现了噢,两种方式,借用Promise、Promise.all、Promise.race 实际上都可以。

实现

// 随机的任务
const paramTasks = Array(~~(Math.random() * 100)).fill(1).map((item, i) => 'task-' + i);
// 并发数
let paramToncurrentNum = 6

// 开始执行任务
function startTasks(tasks, concurrentNum = 6, successCallback) {

    // 状态枚举
    const TaskState = {
        await: 'await',
        run: 'run',
        success: 'success',
        bad: 'bad'
    }

    // 初始化状态任务
    const initStateTask = (task) => {
        return {
            task: typeof task === 'function' ? task: () => task,
            state: TaskState.await, // await, success, bad
            result: null,
            threadName: null,
        }
    }

    // 初始化为带状态和结果的任务
    const stateTasks = [...tasks].map((item) => initStateTask(item))

    // 开始执行任务
    const startTask = (taskList, threadName) => {
        const task = taskList.find((item) => item.state === TaskState.await)
        if (task) {
            task.state = TaskState.run

            return new Promise((resolve, reject) => {
                try {
                    setTimeout(() => {
                        task.state = TaskState.success
                        task.result = task.task()
                        task.threadName = threadName
                        resolve(task)
                        console.log(`任务:${task.result} 执行完毕!执行线程:${task.threadName}!`)
                    })
                } catch (e) {
                    task.state = TaskState.bad
                    task.result = e
                    reject(task)
                    console.log(`任务:${task.result} 执行失败!执行线程:${task.threadName}!`)
                }

            }).finally(() => {
                const task = taskList.find((item) => item.state === TaskState.await)
                if (task) {
                    return startTask(taskList, threadName)
                }
            })
        }
    }

    // 根据并发数,推进任务
    let list = []
    concurrentNum = concurrentNum > stateTasks.length ? stateTasks.length : concurrentNum
    while(concurrentNum --) {
        const threadName = `线程ID:${concurrentNum + 1}`
        list.push(startTask(stateTasks, threadName))
    }

    // 指定并发数的任务执行完毕后,就调用回调函数,把结果返回
    // return Promise.all(list).then(() => stateTasks).then(successCallback).catch(() => successCallback(stateTasks))
    return Promise.race(list).then(() => stateTasks).then(successCallback).catch(() => successCallback(stateTasks))
}

startTasks(paramTasks,paramToncurrentNum, function (tasks) {
    console.log('tasks', tasks)
})

总结

使用Promise.race,会造成所有任务未结束,就直接拿到最终反馈的任务列表对象,由于对象是引用类型,所以等等也能看到所有结果。

image.png

使用Promise.all,可以等到所有任务结束之后,再拿到最终反馈的任务列表对象。

image.png

通过结果化任务模型,可以让你最终得到的反馈列表非常的清晰。

image.png

拓展一下

如果换成fetch请求api接口,可以改成这种,这样可以保证所有请求发送完毕后,拿到所有结果,无论是正常还是异常的都能看到。

function sendRequest(urls: string[], max: number, callback: () => void) {
    // 状态枚举
    const TaskState = {
        await: 'await',
        run: 'run',
        success: 'success',
        bad: 'bad'
    }

    // 初始化状态任务
    const initStateTask = (url) => {
        return {
            url: url,
            state: TaskState.await, // await, run,success, bad
            result: null,
            threadName: null,
        }
    }

    // 初始化为带状态和结果的任务
    const stateTasks = [...urls].map((item) => initStateTask(item))

    // 开始执行任务
    const startTask = (taskList, threadName) => {
        const task = taskList.find((item) => item.state === TaskState.await)
        if (task) {
            task.state = TaskState.run

            return new Promise((resolve, reject) => {
                    fetch(task.url).then(r => r.json()).then(r => {
                        task.state = TaskState.success
                        task.result = r
                        task.threadName = threadName
                        resolve(task)
                        console.log(`任务:${task.result} 执行完毕!执行线程为:${task.threadName}!`)
                    }).catch(e => {
                        task.state = TaskState.bad
                        task.result = e
                        reject(task)
                        console.log(`任务:${task.result} 执行失败!执行线程为:${task.threadName}!`)
                    })
            }).finally(() => {
                const task = taskList.find((item) => item.state === TaskState.await)
                if (task) {
                    return startTask(taskList, threadName)
                }
            })
        }
    }

    // 根据并发数,推进任务
    let list = []
    let concurrentNum = max
    while(concurrentNum --) {
        const threadName = `线程ID:${concurrentNum + 1}`
        list.push(startTask(stateTasks, threadName))
    }

    return Promise.all(list).then(() => stateTasks).then(callback).catch(() => callback(stateTasks))
}

sendRequest(Array(~~(Math.random() * 100)).fill(1).map((item, i) => '/api/url-' + i),6, function (tasks) {
    console.log('tasks', tasks)
})
目录
相关文章
|
3月前
|
前端开发 JavaScript 开发者
揭秘JavaScript魔法三剑客:call、apply、bind,解锁函数新世界,你的前端之路因它们而精彩!
【8月更文挑战第23天】在 JavaScript 的世界里,`call`、`apply` 和 `bind` 这三个方法常常让新手感到困惑。它们都能改变函数执行时的上下文(即 `this` 的指向),但各有特点:`call` 接受一系列参数并直接调用函数;`apply` 则接收一个参数数组,在处理不确定数量的参数时特别有用;而 `bind` 不会立即执行函数,而是创建一个新版本的函数,其 `this` 上下文已被永久绑定。理解这三个方法能帮助开发者更好地运用函数式编程技巧,提升代码灵活性和可维护性。
37 0
|
1月前
|
前端开发 JavaScript
前端:实现一个 sleep 函数
在前端开发中,实现一个 `sleep` 函数可以用来暂停代码执行,模拟延迟效果,常用于测试或控制异步操作的节奏。该函数通常基于 `Promise` 和 `setTimeout` 实现,简单易用。
|
2月前
|
存储 前端开发 JavaScript
前端基础(十二)_函数高级、全局变量和局部变量、 预解析(变量提升)、函数返回值
本文介绍了JavaScript中作用域的概念,包括全局变量和局部变量的区别,预解析机制(变量提升),以及函数返回值的使用和类型。通过具体示例讲解了变量的作用域、函数的返回值、以及如何通过return关键字从函数中返回数据。
23 1
前端基础(十二)_函数高级、全局变量和局部变量、 预解析(变量提升)、函数返回值
|
2月前
|
存储 前端开发 JavaScript
前端基础(十一)_函数声明及调用、函数的形参与实参、arguments参数、函数的参数类型、函数中的问题
本文介绍了JavaScript中函数的声明及调用、形参与实参的概念、arguments对象的使用、函数参数的类型以及函数中this的作用。通过示例代码详细解释了函数如何接收参数、如何处理参数个数不匹配的情况,以及函数在不同上下文中this的指向。
23 1
|
3月前
|
JSON 前端开发 API
构建前端防腐策略问题之更新getMemoryUsagePercent函数以适应新的API返回格式的问题如何解决
构建前端防腐策略问题之更新getMemoryUsagePercent函数以适应新的API返回格式的问题如何解决
构建前端防腐策略问题之更新getMemoryUsagePercent函数以适应新的API返回格式的问题如何解决
|
2月前
|
JavaScript 前端开发
前端JS函数
【9月更文挑战第4天】前端JS函数
27 6
|
2月前
|
前端开发 JavaScript API
前端性能优化-控制并发
【9月更文挑战第7天】前端性能优化-控制并发
31 0
|
4月前
|
开发框架 前端开发 JavaScript
循序渐进VUE+Element 前端应用开发(22)--- 简化main.js处理代码,抽取过滤器、全局界面函数、组件注册等处理逻辑到不同的文件中
循序渐进VUE+Element 前端应用开发(22)--- 简化main.js处理代码,抽取过滤器、全局界面函数、组件注册等处理逻辑到不同的文件中
|
4月前
|
开发框架 JSON 前端开发
循序渐进VUE+Element 前端应用开发(7)--- 介绍一些常规的JS处理函数
循序渐进VUE+Element 前端应用开发(7)--- 介绍一些常规的JS处理函数
|
4月前
|
JavaScript 前端开发
前端框架原理自测题:根据 JSX / Vue 模板写出 render 函数 / VNode
前端框架原理自测题:根据 JSX / Vue 模板写出 render 函数 / VNode
27 0