博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅析setTimeout与Promise
阅读量:5821 次
发布时间:2019-06-18

本文共 3970 字,大约阅读时间需要 13 分钟。

hot3.png

关于JavaScript异步编程,解析过了JavaScript并发模型,该并发模型基于事件循环。正好在Stackoverflow上回答了一个,于是总结这一知识点,与更多读者分享,同时完善JavaScript异步编程系列文章。

前言

我们先看一到常见的前端面试题:

var p1 = new Promise(function(resolve, reject){    resolve(1);})setTimeout(function(){  console.log("will be executed at the top of the next Event Loop");},0)p1.then(function(value){  console.log("p1 fulfilled");})setTimeout(function(){  console.log("will be executed at the bottom of the next Event Loop");},0)

上例代码执行输出顺序如何?这道题也是本文创作的源泉,其答案是:

p1 fulfilledwill be executed at the top of the next Event Loopwill be executed at the bottom of the next Event Loop

接下来展开解释输出结果原因,看完本文应该能了解setTimeout和Promise的区别。

事件循环

事件循环相关详细内容在一文已经介绍过,本文不再赘述,进行一些补充和总结:

事件循环

可执行代码

思考一下,JavaScript代码是如何执行的呢?是一行一行代码执行的吗?当然不是,JavaScript 引擎一块一块地解析,执行JavaScript代码,而非一行一行进行。在解析,执行代码块时,会需要有一个前期工作,如变量/函数提升,定义变量/函数。这里所说的代码块,通常称作可执行代码(execuable code),通常包括全局代码,函数代码,eval执行代码。而所做的前期工作就是创建执行上下文(execution context)。

执行上下文栈

每当JavaScript引擎开始执行应用程序时,都会创建一个执行上下文栈(后进先出),用以管理执行上下文。在执行一段可执行代码时,会创建一个执行上下文,然后将其压入栈,执行完毕便将该上下文退栈。

function funA() {    console.log('funA')}function funB() {    fun3A();}function funC() {    funB();}funC();
ECStack.push(
functionContext);// funC中调用funB,需创建funB执行上下文,入栈ECStack.push(
functionContext);// funB内调用funA,入栈上下文ECStack.push(
functionContext);// funA执行完毕,退栈ECStack.pop();// funB执行完毕,退栈ECStack.pop();// funC执行完毕,退栈ECStack.pop();// javascript继续执行后续代码

另外,所有的代码都是从全局环境开始执行,所以,必然栈底是全局执行上下文。

异步任务

回顾JavaScript事件循环并发模型,我们了解了setTimeoutPromise调用的都是异步任务,这一点是它们共同之处,也即都是通过任务队列进行管理/调度。那么它们有什么区别吗?下文继续介绍。

任务队列

前文已经介绍了任务队列的基础内容和机制,可选择查看,本文对任务队列进行拓展介绍。JavaScript通过任务队列管理所有异步任务,而任务队列还可以细分为MacroTask Queue和MicoTask Queue两类。

MacroTask Queue

MacroTask Queue(宏任务队列)主要包括setTimeout, setInterval, setImmediate, requestAnimationFrame, NodeJS中的`I/O等。

MicroTask Queue

MicroTask Queue(微任务队列)主要包括两类:

  1. 独立回调microTask:如Promise,其成功/失败回调函数相互独立;
  2. 复合回调microTask:如 Object.observe, MutationObserver 和NodeJs中的 process.nextTick ,不同状态回调在同一函数体;

MacroTask和MicroTask

JavaScript将异步任务分为MacroTask和MicroTask,那么它们区别何在呢?

  1. 依次执行同步代码直至执行完毕;
  2. 检查MacroTask 队列,若有触发的异步任务,则取第一个并调用其事件处理函数,然后跳至第三步,若没有需处理的异步任务,则直接跳至第三步;
  3. 检查MicroTask队列,然后执行所有已触发的异步任务,依次执行事件处理函数,直至执行完毕,然后跳至第二步,若没有需处理的异步任务中,则直接返回第二步,依次执行后续步骤;
  4. 最后返回第二步,继续检查MacroTask队列,依次执行后续步骤;
  5. 如此往复,若所有异步任务处理完成,则结束;

task Queue

The microTask queue is processed after callbacks as long as no other JavaScript is mid-execution, and at the end of each task. 只要没有其他JavaScript代码在执行,并且在每个任务结束时,就会开始处理microTask队列。

需要注意的是,此处说的的每个任务结束时中的任务通常就是指macroTask,有一个比较特殊的任务- 脚本执行(JavaScript Run,也是一个macroTask,会在JavaScript脚本执行时,立即将JavaScript Run任务入栈macroTask队列。

回顾

本文内容介绍基本结束,那么前文第一个题目输出顺序是为什么呢?简单解释一下:

  1. 开始执行JavaScript脚本,将任务JavaScript Run入栈macroTask队列;
  2. 同步resolve Promise后;
  3. 入栈第一个setTimeout任务进入macroTask队列
  4. 入栈Promise.then任务进入microTask队列;
  5. 入栈第二个setTimeout任务进入macroTask队列;
  6. 同步执行代码完毕,退出第一个macroTask,即JavaScript Run;
  7. 执行清空microTask;
  8. 执行下一个macroTask;

最后,我们以一个题目再次回顾一下内容:

setTimeout(function(){  console.log("will be executed at the top of the next Event Loop")},0)var p1 = new Promise(function(resolve, reject){    setTimeout(() => { resolve(1); }, 0);});setTimeout(function(){    console.log("will be executed at the bottom of the next Event Loop")},0)for (var i = 0; i < 100; i++) {    (function(j){        p1.then(function(value){           console.log("promise then - " + j)        });    })(i)}

代码输出结果是什么呢?快点确认一下吧:

will be executed at the top of the next Event Looppromise then - 0promise then - 1promise then - 2...promise then - 99will be executed at the bottom of the next Event Loop
  1. 首先同步执行完所有代码,其间注册了三个setTimeout异步任务,100个Promise异步任务;
  2. 然后检查MacroTask队列,取第一个到期的MacroTask,执行输出will be executed at the top of the next Event Loop;
  3. 然后检查MicroTask队列,发现没有到期的MicroTask,进入第4步;
  4. 再次检查MacroTask,执行第二个setTimeout处理函数,resolve Promise;
  5. 然后检查MicroTask队列,发现Promise已解决,其异步处理函数均可执行,依次执行,输出promise then - 0promise then - 99
  6. 最后再次检查MacroTask队列,执行输出will be executed at the bottom of the next Event Loop
  7. 交替往复检查两个异步任务队列,直至执行完毕;

转载于:https://my.oschina.net/u/3451529/blog/1929152

你可能感兴趣的文章
zabbix 批量web url监控
查看>>
MongoDB CookBook读书笔记之导入导出
查看>>
shell如何快速锁定所有账号
查看>>
HTML 5实现的手机摇一摇
查看>>
Linux 文件IO理解
查看>>
Ninject 2.x细说---2.绑定和作用域
查看>>
30个非常时尚的网页联系表单设计优秀示例
查看>>
使用membership(System.Web.Security)来进行角色与权限管理
查看>>
opticom 语音质量验证白皮书
查看>>
3D实时渲染中的BSP树和多边形剔除
查看>>
Frank Klemm's Dither and Noise Shaping Page: Dither and Noise Shaping In MPC/MP+
查看>>
网络抓包的部署和工具Wireshark【图书节选】
查看>>
Redis在Windows+linux平台下的安装配置
查看>>
Maven入门实战笔记-11节[6]
查看>>
Local declaration of 'content' hides instance variable
查看>>
ASP.NET中 HTML标签总结及使用
查看>>
Linux下日志系统的设计
查看>>
爬虫IP被禁的简单解决方法——切换UserAgent
查看>>
selenium + python 添加等待时间
查看>>
php生成word,并下载
查看>>