js异步控制器,实现异步同步代码交替穿插执行

一、js代码中发起请求,读取文件等操作都是异步执行,如果我们希望将多个异步函数的结果都收集起来,统一处理,这应该怎么做呢?

第一种方法,需要用async和await关键字将异步函数强制为同步状态,这样就可以同步获取每一个异步函数的结果了。(耗时长)

第二种就是使用Promise对象,将每个函数都装到Promise对象中,按照顺序放到.then方法中,变成串行执行,(耗时长)

第三种方法,需要定义全局变量,标记每个异步函数的执行结果状态,定义全局容器收集异步函数执行结果,定义全局检查函数检查执行结果状态,然后在每一个异步函数的完成的时候修改该状态,并且调用检查方法,在全部结束的时候表示异步函数结果收集完成。(代码复杂度高)

以上方式即可解决

二、如果希望多个异步并行,全部结束之后又需要连续几个异步串行,之后又希望多个异步函数并行,又希望多个异步串行等等,这样的话,通过上面的方法,也可以解决,但代码都会变得特别复杂,难以维护,怎么办?

于是乎,想到应该得有这么一个异步函数控制器,想串行就串行,想并行就并行,而且代码复杂度还得降低,网上搜索半天没有找到现成的代码,或者就是晦涩难懂。

思路如下:

每一个异步函数都看做成一个任务,当然同步函数亦可,这里借鉴批量程序的任务单元概念:

一个任务定义简单的生命周期,即任务初始化,任务执行中,任务结束。

这里先定义好枚举常量,增强代码易读性

/*** 任务执行状态枚举类* @type {{INIT: number, RUNNING: number, FINISH: number}}*/
const TaskStatusEnum = {INIT: -1, //初始化RUNNING: 0, //执行中FINISH: 1 //已完成
}

我们现在面向异步函数,如何知道异步函数这个任务什么时候结束?代码执行完最后一行就算结束?不对吧,代码执行完最后一行,异步函数似乎并没有结束。

而且js中也没办法去检测一个函数到底有没有结束,就算可以,那什么时机去检测呢?定时轮训可以不,异步的不确定性,定多少时间合适呢?1秒检测一次?还是1毫秒一次?检测频率越慢,这中间会造成延迟越大,检测越快,越影响代码执行效率,因为js是单线程。

我在刚学js的时候,会碰到这些疑问,原因是习惯性按照同步的思路去思考问题。

但js有个概念:事件驱动。比如:根据工作任务分配原则,A同事的工作完成后,我们要让A主动汇报自己已经完成工作了,而不是每次让我们一次又一次观察A的情况。A的汇报就是事件驱动,我们给A交代了一个事情(每次工作完要主动汇报)。

回归正题:我们需要让异步函数工作完成,主动通知自己工作已经完成。这里又是一个面向对象编程的思想,我们定义一个任务类:

类的实例化被看做任务初始化

类中提供一个开始执行的方法,控制任务的执行时机,修改自己的生命周期为正在执行

类中还提供一个结束的方法,用于任务执行过程中随时可主动修改自己的生命周期为已结束

代码如下:

/*** 任务单元* @param taskId 任务id* @param task   任务函数* @param group   任务组对象* @constructor*/
function TaskUnit(taskId, task, group) {this.taskId = taskId;  //任务唯一标识this.status = TaskStatusEnum.INIT; //执行状态:-1初始化  0执行中  1已完成this.task = task; //任务内容/*** 执行任务内容*/this.execute = () => {this.status = TaskStatusEnum.RUNNING;this.task(this.end);}/*** 任务完成回调*/this.end = () => {if (this.status === TaskStatusEnum.RUNNING) {this.status = TaskStatusEnum.FINISHgroup.check();}}
}

额(⊙o⊙)…目测这个类,好像和Promise长得差不多。少了异常捕获catch和finally两个函数,这两个不是我任务单元所关心的,因此这些东西交给实际任务内容去处理。

多的是什么呢?任务并不是实例化之后被立刻调用,而是通过execute函数,让任务可在任意时机被调用,不受代码位置的限制。

这样的好处是什么?我可以在一瞬间添加无数个任务,但执行的时候我可以控制想让哪几个任务一起执行,就可以一起执行,不想让谁执行,谁就执行不了,想什么时机执行就什么时机执行。实现异步串行。

那可能有人会问,就凭这两段代码好像做不到异步串行吧。确实,这怎么解决?

有一个方法就是可以给任务加标识,即标识为1的都可以同时执行,标识为2的必须等所有的1结束了才能执行。标识为2的任务怎么知道标识1的任务什么时候结束呢?当然要让标识1的任务主动通知,设置检查函数,每一个标识1的任务结束后都主动调用检查函数,发现全部执行完毕后,就开始调用标识2的任务,这样就实现了异步串行了。

补:因为js是单线程,不会存在线程并发资源抢占的问题,一定有一个任务会检测到所有任务都结束了。在多线程编程的语言中,需要在检查的函数中加把锁。

加标识的方法存在局限性,哪里有那么多标识可加,代码得多混乱.....

因此我们又引入了任务组的概念:即可以同时一起执行的任务把它们归成一组,这样就会分成很多组,这个时候把任务组看成一个不可分割的整体,保证任务组的有序执行即可。如何保证有序,构建任务组队列,或者任务组链表,依次执行即可。

再分析任务组内的所有任务如何保证全部执行完毕:让每个任务结束都通知任务组检查,总会有一个任务检查的时候发现任务都结束了

代码如下:

/*** 任务组* @constructor*/
function TaskGroup() {this.nextGroup=null;//下一个任务组this.taskList=[];//任务列表this.add=(task)=>{this.taskList.push(task);}this.setNextGroup=(group)=>{this.nextGroup=group;}/*** 启动任务组*/this.start=()=>{for (let i = 0; i < this.taskList.length; i++) {const task=this.taskList[i];if (task.status === TaskStatusEnum.INIT) {task.execute();//执行}}}/*** 检查任务*/this.check=()=>{for (let i = 0; i < this.taskList.length; i++) {if (this.taskList[i].status !== TaskStatusEnum.FINISH) {return; //发现还有任务没有执行完成}}if(this.nextGroup)this.nextGroup.start();//任务全部执行完成,进行下一个任务组}}

最后创建一个任务中心,用来提供对外接口,将任务封装成一个个任务,组成任务组,控制启动结束等等。

三、总代码如下:asyncController.js

/*** 任务执行状态枚举类* @type {{INIT: number, RUNNING: number, FINISH: number}}*/
const TaskStatusEnum = {INIT: -1, //初始化RUNNING: 0, //执行中FINISH: 1 //已完成
}/*** 任务单元* @param taskId 任务id* @param task   任务函数* @param group   任务组对象* @constructor*/
function TaskUnit(taskId, task, group) {this.taskId = taskId;  //任务唯一标识,暂时没用到this.status = TaskStatusEnum.INIT; //执行状态:-1初始化  0执行中  1已完成this.task = task; //任务内容/*** 执行任务内容*/this.execute = () => {this.status = TaskStatusEnum.RUNNING;this.task(this.end);}/*** 任务完成回调*/this.end = () => {if (this.status === TaskStatusEnum.RUNNING) {this.status = TaskStatusEnum.FINISHgroup.check();}}
}/*** 任务组* @constructor*/
function TaskGroup() {this.nextGroup=null;//下一个任务组this.taskList=[];//任务列表this.add=(task)=>{this.taskList.push(task);}this.setNextGroup=(group)=>{this.nextGroup=group;}/*** 启动任务组*/this.start=()=>{for (let i = 0; i < this.taskList.length; i++) {const task=this.taskList[i];if (task.status === TaskStatusEnum.INIT) {task.execute();//执行}}}/*** 检查任务*/this.check=()=>{for (let i = 0; i < this.taskList.length; i++) {if (this.taskList[i].status !== TaskStatusEnum.FINISH) {return; //发现还有任务没有执行完成}}if(this.nextGroup)this.nextGroup.start();//任务全部执行完成,进行下一个任务组}}/*** 异步函数控制器,* .and() 添加异步并行函数* .next() 添加同步串行函数*/
module.exports = function () {this.queue = [];this.nowTaskGroup=null;//当前任务组this.startCount = 0;//加入任务数/*** 调用该函数表示添加并行任务* @param task 任务*/this.and = (task) => {if(this.nowTaskGroup==null){this.nowTaskGroup=new TaskGroup();}this.nowTaskGroup.add(new TaskUnit(++this.startCount, task, this.nowTaskGroup))return this}/*** 调用该函数表示添加串行任务* @param task 任务*/this.next = (task) => {if(this.nowTaskGroup!=null) this.queue.push(this.nowTaskGroup);//防止上一个添加的任务是并行的this.nowTaskGroup=new TaskGroup();this.nowTaskGroup.add(new TaskUnit(++this.startCount, task, this.nowTaskGroup));this.queue.push(this.nowTaskGroup);this.nowTaskGroup=null;//当前任务添加结束清空return this}/*** 调用该函数表示任务添加完毕,开始执行任务* @param endTask 任务全部结束后回调*/this.finish = (endTask) => {if(this.nowTaskGroup!=null) this.queue.push(this.nowTaskGroup);this.nowTaskGroup=new TaskGroup();this.nowTaskGroup.add(new TaskUnit(++this.startCount, endTask, this.nowTaskGroup));this.queue.push(this.nowTaskGroup);this.nowTaskGroup=null;//当前任务添加结束清空//组装成有向链表for(let i=0;i<this.queue.length-1;i++){this.queue[i].setNextGroup(this.queue[i+1]);}this.queue[0].start();//启动链表首个任务组this.queue=[] //清空任务,为下一波任务做准备}
}

四、使用样例:

const Controller=require('./src/util/asyncController')const controller=new Controller();
controller.and(end=>{setTimeout(()=>{console.log("并行1")end();},2000);
}).and(end=>{setTimeout(()=>{console.log("并行2")end();},2000);
}).and(end=>{setTimeout(()=>{console.log("并行3")end();},2000);
}).next(end=>{setTimeout(()=>{console.log("串行1")end();},2000);
}).next(end=>{setTimeout(()=>{console.log("串行2")end();},2000);
}).and(end=>{setTimeout(()=>{console.log("并行4")end();},2000);
}).and(end=>{setTimeout(()=>{console.log("并行5")end();},2000);
}).next(end=>{setTimeout(()=>{console.log("串行3")end();},2000);
}).finish(()=>{setTimeout(()=>{console.log("结束")},2000);
})

 执行结果

"C:\Program Files\nodejs\node.exe" main.js
并行1
并行2
并行3
串行1
串行2
并行4
并行5
串行3
结束

Process finished with exit code 0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/71650.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kafka的文件存储与稀疏索引机制

![在这里插入图片描述](https://img-blog.csdnimg.cn/dde7fc866d214985baaa87300a472578.png)这些是存储在分区(分区才是实际的存储)文件中的. seg是逻辑概念 而实际由log存储的. index是偏移量索引而timeindex是时间戳索引 log就是seg 找数据就是先找log 再从log去找

MYSQL 高级SQL语句

1、按关键字排序&#xff1a; order by 语句用来实现 &#xff0c;前面可以使用where字句使查询结果进一步过滤 asc 是按照升序排序 &#xff0c; 默认的 desc 是按照降序排序 order by的语法结构 例&#xff1a;select name,score from ku order by score desc; 表示将数…

数字图像处理-形态学图像处理

形态学图像处理 一、基础知识1.1 什么是形态学操作 二、腐蚀与膨胀2.1 腐蚀2.2 膨胀 三、开操作与闭操作3.1 开操作3.2 闭操作3.3 实验对比 四、一些基本的形态学算法4.1边界提取4.2空洞填充4.3 凸壳 一、基础知识 1.1 什么是形态学操作 数字图像处理中的形态学操作是一组用于…

Linux CPU线程绑核

为了加快程序的运行速度和充分利用CPU资源&#xff0c;我们可以人为将不同线程绑定在不同的cup上&#xff0c;例如有两个线程A,B&#xff0c;其中A已经在CPU0上运行&#xff0c;并且CPU0上还有其他的任务&#xff0c;那么我们可以将线程B绑到CPU1上&#xff0c;这样就可以减轻C…

链路追踪Skywalking快速入门

目录 1 Skywalking概述1.1 微服务系统监控三要素1.2 什么是链路追踪1.2.1 链路追踪1.2.2 OpenTracing1、数据模型&#xff1a;2、核心接口语义 1.3 常见APM系统1.4 Skywalking介绍1、SkyWalking 核心功能&#xff1a;2、SkyWalking 特点&#xff1a;3、Skywalking架构图&#x…

Medium: 9 Important Things to Remember for AB Test

There are lots of things that can go wrong [存在隐患] when you try to create AB tests. Goal & Motivation Making it clear helps align the team toward the goal. Hypothesis Your hypothesis should be short and to the point. It should not run into a coup…

直播平台源码开发搭建APP的DASH协议:流媒体技术其中一环

在直播平台源码APP中&#xff0c;有着许许多多、多种多样的功能&#xff0c;比如短视频功能&#xff0c;帮助我们去获取信息&#xff0c;看到全世界用户身边发生的事情或是他们的生活&#xff1b;又比如直播功能&#xff0c;为用户提供了实时的娱乐享受&#xff0c;还让一些用户…

【JavaEE基础学习打卡07】JDBC之应用分层设计浅尝!

目录 前言一、简单说说应用分层二、实体层1.O/R映射2.O/R映射实践三、数据访问层1.DAO层2.DAO层实战总结前言 📜 本系列教程适用于JavaWeb初学者、爱好者,小白白。我们的天赋并不高,可贵在努力,坚持不放弃。坚信量最终引发质变,厚积薄发。 🚀 文中白话居多,尽量以小白…

EVA: Visual Representation Fantasies from BAAI

本文做个简单总结&#xff0c;博主不是做自监督领域的&#xff0c;如果错误&#xff0c;欢迎指正。 链接 Code&#xff1a; Official&#xff1a;baaivision/EVA MMpretrain&#xff1a;open-mmlab/mmpretrain/tree/main/configs/eva02 Paper&#xff1a; EVA01&#xff1a;…

deepfm内容理解

对于CTR问题&#xff0c;被证明的最有效的提升任务表现的策略是特征组合(Feature Interaction)&#xff1b; 两个问题&#xff1a; 如何更好地学习特征组合&#xff0c;进而更加精确地描述数据的特点&#xff1b; 如何更高效的学习特征组合。 DNN局限 &#xff1a;当我们使…

vue-别名路径联想提示的配置

在根路径下&#xff0c;新建 jsconfig.json 文件&#xff0c;即可 在输入 自动联想到src目录。 代码如下&#xff1a; // 别名路径联想提示&#xff1a;输入自动联想 {"compilerOptions":{"baseUrl":"./","paths": {"/*":[…

基于github上go版本的LoraWAN Server安装及使用

一、安装环境 该版本的LoraWAN Server基于Ubuntu 20.04.6 LTS x86_x64 版本安装 当然也可以在Windows环境中进行安装使用&#xff0c;此处只针对测试使用的Linux的环境进行简要说明&#xff1b; 二、Git源码下载及安装 2.1 下载地址 Github上的下载地址如下&#xff1a;GitHub…

stable diffusion实践操作-提示词-整体环境

系列文章目录 stable diffusion实践操作-提示词 文章目录 系列文章目录前言一、提示词汇总1.1 整体环境11.2 整体环境1 二 、总结 前言 本文主要收纳总结了提示词-整体环境。 一、提示词汇总 1.1 整体环境1 画质背景场景画风镜头[最高质量][透明背景][山][轮廓加深][正面视…

从jdk8 升级到jdk17的问题总结

目录 1. java.lang.reflect.InaccessibleObjectException: 2. java.lang.UnsatisfiedLinkError in autosys 3. java.lang.NoClassDefFoundError: Could not initialize class net.sf.jasperreports.engine.util.JRStyledTextParser 4. java.lang.UnsatisfiedLinkError: **…

【AI理论学习】语言模型:从Word Embedding到ELMo

语言模型&#xff1a;从Word Embedding到ELMo ELMo原理Bi-LM总结参考资料 本文主要介绍一种建立在LSTM基础上的ELMo预训练模型。2013年的Word2Vec及2014年的GloVe的工作中&#xff0c;每个词对应一个vector&#xff0c;对于多义词无能为力。ELMo的工作对于此&#xff0c;提出了…

ChartJS使用-环境搭建(vue)

1、介绍 Chartjs简约不简单的JavaScript的图表库。官网https://chart.nodejs.cn/ Chart.js 带有内置的 TypeScript 类型&#xff0c;并与所有流行的 JavaScript 框架 兼容&#xff0c;包括 React 、Vue 、Svelte 和 Angular 。 你可以直接使用 Chart.js 或利用维护良好的封装程…

Ros noetic 机器人坐标记录运动路径和发布 实战教程(B)

前言: 网上记录Path的写入文件看了一下还挺多的,有用yaml作为载体文件,也有用csv文件的路径信息,也有用txt来记录当前生成的路径信息,载体不重要,反正都是记录的方式,本文主要按yaml的方式写入,本文将撰写csv方式的文件写入格式,完成上一篇文章中的留下的存入…

CentOS 7 编译ZooKeeper C客户端

简介 本文主要讲解&#xff1a;Zookeeper C客户端库在Centos 7上的编译&#xff0c;使用的Zookeeper版本为3.4.13。 工具安装 安装ant 和cppunit-devel工具&#xff1a; [rootlocalhost source_code]# yum install -y cppunit-devel 已加载插件&#xff1a;fastestmirror L…

【Java Web】实现帖子点赞功能——基于Redis

点赞 支持对帖子、评论点赞&#xff1b;第一次点赞&#xff0c;第二次点赞取消&#xff1b; 首页显示点赞数量 统计帖子点赞数量&#xff1b; 详情页显示点赞数量 统计点赞数量&#xff1b;显示点赞状态&#xff1b; 1. LikeService定义一些关于点赞的操作 点赞&#xff1a;…

Git上传新项目

第一步&#xff1a;初始化 Git 仓库 首先&#xff0c;打开终端或命令行界面&#xff0c;然后导航到项目目录。运行下面的命令来初始化一个新的 Git 仓库&#xff1a; git init这将创建一个新的 .git 子目录&#xff0c;其中包含了初始化的 Git 仓库。 第二步&#xff1a;添加…