Js异步机制的实现
Js异步机制
JavaScript
是一门单线程语言,所谓单线程,就是指一次只能完成一件任务,如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。这种模式的好处是实现起来比较简单,执行环境相对单纯,坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应也就是假死状态,往往就是因为某一段Javascript
代码长时间运行比如死循环,导致整个页面卡在这个地方,其他任务无法执行。
执行机制
为了解决上述问题,Javascript
将任务的执行模式分为两种:同步Synchronous
与异步Asynchronous
,同步或非同步,表明着是否需要将整个流程按顺序地完成,阻塞或非阻塞,意味着你调用的函数会不会立刻告诉你结果
同步
同步模式就是同步阻塞,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。
var i = 100;
while(--i) { console.log(i); }
console.log("while 执行完毕我才能执行");
异步
异步执行就是非阻塞模式执行,每一个任务有一个或多个回调函数callback
,前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。浏览器对于每个Tab
只分配了一个Js
线程,主要任务是与用户交互以及操作DOM
等,而这也就决定它只能为单线程,否则会带来很复杂的同步问题,例如假定JavaScript
同时有两个线程,一个线程在某个DOM
节点上添加内容,另一个线程删除了这个节点,这时浏览器无法确定以哪个线程的操作为准。
setTimeout(() => console.log("我后执行"), 0);
// 注意:W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms,此外这与浏览器设定、主线程以及任务队列也有关系,执行时间可能大于4ms,例如老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动尤其是涉及页面重新渲染的部分,通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。
console.log("我先执行");
异步机制
首先来看一个例子,与上文一样来测试一个异步执行的操作
setTimeout(() => console.log("我在很长时间之后才执行"), 0);
var i = 3000000000;
while(--i) { }
console.log("循环执行完毕");
本地测试,设置的setTimeout
回调函数大约在30s
之后才执行,远远大于4ms
,我在主线程设置了一个非常大的循环来阻塞Js
主线程,注意我并没有设置一个死循环,假如我在此处设置死循环来阻塞主线程,那么设置的setTimeout
回调函数将永远不会执行,此外由于渲染线程与JS
引擎线程是互斥的,Js
线程在处理任务时渲染线程会被挂起,整个页面都将被阻塞,无法刷新甚至无法关闭,只能通过使用任务管理器结束Tab
进程的方式关闭页面。
Js
实现异步是通过一个执行栈与一个任务队列来完成异步操作的,所有同步任务都是在主线程上执行的,形成执行栈,任务队列中存放各种事件回调(也可以称作消息),当执行栈中的任务处理完成后,主线程就开始读取任务队列中的任务并执行,不断往复循环。
例如上例中的setTimeout
完成后的事件回调就存在任务队列中,这里需要说明的是浏览器定时计数器并不是由JavaScript
引擎计数的,因为JavaScript
引擎是单线程的,如果线程处于阻塞状态就会影响记计时的准确,计数是由浏览器线程进行计数的,当计数完毕,就将事件回调加入任务队列,同样HTTP
请求在浏览器中也存在单独的线程,也是执行完毕后将事件回调置入任务队列。通过这个流程,就能够解释为什么上例中setTimeout
的回调一直无法执行,是由于主线程也就是执行栈中的代码没有完成,不会去读取任务队列中的事件回调来执行,即使这个事件回调早已在任务队列中。
Event Loop
主线程从任务队列中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop
,Event Loop
是一个执行模型,在不同的地方有不同的实现,浏览器和NodeJS
基于不同的技术实现了各自的Event Loop
。浏览器的Event Loop
是在HTML5
的规范中明确定义,NodeJS
的Event Loop
是基于libuv
实现的。
在浏览器中的Event Loop
由执行栈Execution Stack
、后台线程Background Threads
、宏队列Macrotask Queue
、微队列Microtask Queue
组成。
- 执行栈就是在主线程执行同步任务的数据结构,函数调用形成了一个由若干帧组成的栈。
- 后台线程就是浏览器实现对于
setTimeout
、setInterval
、XMLHttpRequest
等等的执行线程。 - 宏队列,一些异步任务的回调会依次进入宏队列,等待后续被调用,包括
setTimeout
、setInterval
、setImmediate(Node)
、requestAnimationFrame
、UI rendering
、I/O
等操作 - 微队列,另一些异步任务的回调会依次进入微队列,等待后续调用,包括
Promise
、process.nextTick(Node)
、Object.observe
、MutationObserver
等操作
当Js
执行时,进行如下流程
- 首先将执行栈中代码同步执行,将这些代码中异步任务加入后台线程中
- 执行栈中的同步代码执行完毕后,执行栈清空,并开始扫描微队列
- 取出微队列队首任务,放入执行栈中执行,此时微队列是进行了出队操作
- 当执行栈执行完成后,继续出队微队列任务并执行,直到微队列任务全部执行完毕
- 最后一个微队列任务出队并进入执行栈后微队列中任务为空,当执行栈任务完成后,开始扫面微队列为空,继续扫描宏队列任务,宏队列出队,放入执行栈中执行,执行完毕后继续扫描微队列为空则扫描宏队列,出队执行
- 不断往复...
实例
// Step 1
console.log(1);
// Step 2
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3);
});
}, 0);
// Step 3
new Promise((resolve, reject) => {
console.log(4);
resolve();
}).then(() => {
console.log(5);
})
// Step 4
setTimeout(() => {
console.log(6);
}, 0);
// Step 5
console.log(7);
// Step N
// ...
// Result
/*
1
4
7
5
2
3
6
*/
Step 1
// 执行栈 console
// 微队列 []
// 宏队列 []
console.log(1); // 1
Step 2
// 执行栈 setTimeout
// 微队列 []
// 宏队列 [setTimeout1]
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3);
});
}, 0);
Step 3
// 执行栈 Promise
// 微队列 [then1]
// 宏队列 [setTimeout1]
new Promise((resolve, reject) => {
console.log(4); // 4 // Promise是个函数对象,此处是同步执行的 // 执行栈 Promise console
resolve();
}).then(() => {
console.log(5);
})
Step 4
// 执行栈 setTimeout
// 微队列 [then1]
// 宏队列 [setTimeout1 setTimeout2]
setTimeout(() => {
console.log(6);
}, 0);
Step 5
// 执行栈 console
// 微队列 [then1]
// 宏队列 [setTimeout1 setTimeout2]
console.log(7); // 7
Step 6
// 执行栈 then1
// 微队列 []
// 宏队列 [setTimeout1 setTimeout2]
console.log(5); // 5
Step 7
// 执行栈 setTimeout1
// 微队列 [then2]
// 宏队列 [setTimeout2]
console.log(2); // 2
Promise.resolve().then(() => {
console.log(3);
});
Step 8
// 执行栈 then2
// 微队列 []
// 宏队列 [setTimeout2]
console.log(3); // 3
Step 9
// 执行栈 setTimeout2
// 微队列 []
// 宏队列 []
console.log(6); // 6
参考
https://www.jianshu.com/p/1a35857c78e5
https://segmentfault.com/a/1190000016278115
https://segmentfault.com/a/1190000012925872
https://www.cnblogs.com/sunidol/p/11301808.html
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop
Js异步机制的实现的更多相关文章
- 从一到面试题了解js异步机制:setTimeout 和 Pronmise
1.毫无疑问setTimeout是最晚输出的 2.请无视undefined,这是浏览器的返回值. 3.new Promise中并不是异步,而.then()后才是异步.
- JavaScript单线程和异步机制
随着对JavaScript学习的深入和实践经验的积累,一些原理和底层的东西也开始逐渐了解.早先也看过一些关于js单线程和事件循环的文章,不过当时看的似懂非懂,只留了一个大概的印象:浏览器中的js程序时 ...
- 深入理解node.js异步编程:基础篇
###[本文是基础内容,大神请绕道,才疏学浅,难免纰漏,请各位轻喷] ##1. 概述 目前开源社区最火热的技术当属Node.js莫属了,作为使用Javascript为主要开发语言的服务器端编程技术和平 ...
- node.js零基础详细教程(4):node.js事件机制、node异步IO操作
第四章 建议学习时间3小时 课程共10章 学习方式:详细阅读,并手动实现相关代码 学习目标:此教程将教会大家 安装Node.搭建服务器.express.mysql.mongodb.编写后台业务逻辑. ...
- 浅析JS异步执行机制
前言 JS异步执行机制具有非常重要的地位,尤其体现在回调函数和事件等方面.本文将针对JS异步执行机制进行一个简单的分析. 从一份代码讲起 下面是两个经典的JS定时执行函数,这两个函数的区别相信对JS有 ...
- js异步处理工作机制
js异步处理工作机制 从基础的层面来讲,理解JavaScript的定时器是如何工作的是非常重要的.计时器的执行常常和我们的直观想象不同,那是因为JavaScript引擎是单线程的.我们先来认识一下 ...
- 面试 09-02.js运行机制:异步和单线程
09-02.js运行机制:异步和单线程 #前言 面试时,关于同步和异步,可能会问以下问题: 同步和异步的区别是什么?分别举一个同步和异步的例子 一个关于 setTimeout 的笔试题 前端使用异步的 ...
- JS魔法堂:深究JS异步编程模型
前言 上周5在公司作了关于JS异步编程模型的技术分享,可能是内容太干的缘故吧,最后从大家的表情看出"这条粉肠到底在说啥?"的结果:(下面是PPT的讲义,具体的PPT和示例代码在h ...
- C“中断” 与 JS“异步回调” 横向对比
在底层C语言中,有一个非常重要而特别的概念,叫做“中断”.用比喻来说,我正在写着博客,突然我妈打个电话过来,我就离开了键盘去接电话了,然后写博客就中断了,我聊完电话回来再继续写.乍一听似乎并没有什么大 ...
随机推荐
- GPU Skinning不生效问题
1)GPU Skinning不生效问题2)勾选凸包报的警告问题3)Unity 2019 图片压缩格式选择4)Android Export打包对压缩的影响5)Android内存中的Unknown部分泄漏 ...
- GO语言web框架Gin之完全指南(一)
作为一款企业级生产力的web框架,gin的优势是显而易见的,高性能,轻量级,易用的api,以及众多的使用者,都为这个框架注入了可靠的因素.截止目前为止,github上面已经有了 35,994 star ...
- docker系列详解<二>之常用命令
此篇我们以从docker运行一个tomcat为例,进行一下操作: 拉取镜像 查看镜像 创建容器 查看运行状态 进入退出容器 停止容器 重启容器 删除容器 删除镜像 1.拉取tomcat镜像: 1).查 ...
- NeurIPS 2019 | 基于Co-Attention和Co-Excitation的少样本目标检测
论文提出CoAE少样本目标检测算法,该算法使用non-local block来提取目标图片与查询图片间的对应特征,使得RPN网络能够准确的获取对应类别对象的位置,另外使用类似SE block的sque ...
- 使用@property 添加width 和 height 属性
@property Python内置的@property装饰器就是负责把一个方法变成属性调用的: class Screen(object): def isint(self,px): if not is ...
- pytorch tensor的索引与切片
切片方式与numpy是类似. * a[:2, :1, :, :], * 可以用-1索引. * ::2,表示所有数据,间隔为2,即 start:end:step. * a.index_select(1 ...
- 非常详细的 Linux C/C++ 学习路线总结!已拿腾讯offer
创作不易,点赞关注支持一下吧,我的更多原创技术分享,关注公众号「后端技术学堂」第一时间看! 最近在知乎经常被邀请回答类似如何学习C++和C++后台开发应该具体储备哪些基础技能的问题. 本身我从事的的C ...
- 使用 PyTorch 进行 风格迁移(Neural-Transfer)
1.简介 本教程主要讲解如何实现由 Leon A. Gatys,Alexander S. Ecker和Matthias Bethge提出的Neural-Style 算法.Neural-Style 或者 ...
- Java生鲜电商平台-电商中"再来一单"功能架构与详细设计(APP/小程序)
Java生鲜电商平台-电商中"再来一单"功能架构与详细设计(APP/小程序) 说明:在实际的业务场景中(无论是TO B还是TO C)不管是休闲食品.餐饮.水果.日用百货.母婴等高频 ...
- IEnumerable和IQueryable在使用时的区别
最近在调研数据库查询时因使用IEnumerable进行Linq to entity的操作,造成数据库访问缓慢.此文讲述的便是IEnumerable和IQueryable的区别. 微软对IEnumera ...