鱼C论坛

 找回密码
 立即注册
查看: 2655|回复: 14

[庖丁解牛] 0 2 3 2 ★ JS执行机制大作战 |【promise...then】

[复制链接]
发表于 2018-12-10 13:58:33 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 不二如是 于 2018-12-16 11:23 编辑




                               
登录/注册后可看大图


在上一讲我们介绍了JS的回调,并提到了JS是单线程,这次好好科普一下JS中的执行机制(Event Loop)。

为什么 JS 是单线程的?

作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM 。

这决定了它只能是单线程,否则会带来很复杂的同步问题。

比如:
假定 JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点。

这时浏览器应该以哪个线程为准?


很明显,多线作战会带来很多麻烦,那咋办?

giphy.gif


将JS执行机制设计为单线程好了,这就意味着:
所有任务都需要排队,前一个任务结束,才能执行后一个任务。


一个一个的执行,也带了一个潜在风险:
如果前一个任务耗时很长,那么后一个任务就不得不一直等待。


于是乎,JS 设计者们把所有任务分成两类:
游客,如果您要查看本帖隐藏内容请回复


同步:只有前一个任务执行完毕,才能执行后一个任务。

异步:当同步任务执行到某个 WebAPI 时,就会触发异步操作,此时浏览器会单独开线程去处理这些异步任务。

这两个概念,小甲鱼老师有讲过,再发一次传送门:006 - 为网页注入灵魂

稍微解释下WebAPI

浏览器事件、定时器、ajax,这些操作不会阻塞 JS 的执行,JS 会跳过当前代码,执行后续代码。

任务队列( Task Queue ):主线程执行完毕后所触发的异步任务( WebAPIs ),叫任务队列。

回调队列( Callback Queue ):这些异步 WebAPI 执行完成后得到的结果,会添加到 callback queue 中。

事件循环( Event Loop ):只要主线程的同步任务执行完毕,就会不断的读取 "回调队列" 中的回调函数,到主线程中执行,这个过程不断循环往复。

如何知道主线程执行执行完毕?

JS引擎存在 monitoring process 进程,会持续不断的检查主线程执行为空。

一旦为空,就会去 callback queue 中检查是否有等待被调用的函数。

没办法,必须铺垫一些概念,先来上代码:
  1. console.log("1");
  2.    
  3.     // 定时器
  4.     setTimeout(function () {
  5.         console.log("2");
  6.     },1111);
  7.    
  8.     console.log("3");
复制代码


输出结果:
Snip20181210_80.png


先打印1,浏览器正常的开始流程。

遇到 WebAPI( setTimeout ) ,浏览器新开定时器线程处理,执行完成后把回调函数存放到回调队列中。

专业一点的说发:
JS 引擎遇到异步任务后不会一直等待其返回结果,而是将这个任务挂起交給其他浏览器线程处理,自己继续执行主线程中的其他任务。


这个异步任务执行完毕后,把结果返回给回调队列。

被放入的代码不会被立即执行。

而是当主线程所有同步任务执行完毕, monitoring process 进程就会把 "回调队列" 中的第一个回调代码放入主线程。

然后主线程执行代码,如此反复。

打印3异步 setTimeout 不会阻塞同步代码,因此会首先打印3。

主线程执行完毕后,执行 Callback Queue 打印2

上面这个流程意味着异步执行任务是有优先级滴!




宏任务与微任务

异步任务的执行优先级并不相同,它们被分为两类:微任务( micro task ) 和 宏任务( macro task ) 。

宏任务( macro-task ):整体 script、setTimeout、setInterval、UI交互事件、I/O。

微任务( micro-task ):process.nextTick、Promise、MutaionObserver。

根据异步事件的类型,这些事件实际上会被派发对应的宏任务和微任务中。

在当前主线程执行完毕后,会先查看微任务中是否有事件存在,如果不存在,则再去找宏任务。

如果存在,则会依次执行队列中的参数,直到微任务列表为空,让后去宏任务中一次读取事件到主线程中执行,如此反复。

当前主线程执行完毕后,会首先处理微任务队列中的事件,让后再去读取宏任务队列的事件。

在同一次事件循环中,微任务永远在宏任务之前执行。


概念讲完,我们拿上面的代码领悟下。

setTimeout是宏任务,正常的console.log()是微任务,所以在执行输出1后,setTimeout不会立即执行,等待第二个微任务完成,即打印3

主线程执行完毕后,执行 Callback Queue 最终打印2

还不过瘾?

再来一段代码之前,再来科普一个很厉害的方法。




Promise的含义

Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

从语法上说,Promise是一个对象,从它可以获取异步操作的消息。

Promise提供统一的API,各种异步操作都可以用同样的方法进行处理,是ES6中新加的一个方法。

实例:
  1. var promise = new Promise(function(resolve, reject) {
  2.   // ... some code

  3.   if (/* 异步操作成功 */){
  4.     resolve(value);
  5.   } else {
  6.     reject(error);
  7.   }
  8. });
复制代码


Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。

它们是两个函数,由JavaScript引擎提供,不用自己定义。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。

reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数,实例:
  1. promise.then(function(value) {
  2.   // success
  3. }, function(value) {
  4.   // failure
  5. });
复制代码


then方法可以接受两个回调函数作为参数。

第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。

其中,第二个函数是可选的,不一定要提供。

这两个函数都接受Promise对象传出的值作为参数。

实例:
  1. let promise = new Promise(function(resolve, reject) {
  2.     console.log('promise-Promise');
  3.     resolve();
  4. });

  5. promise.then(function() {
  6.     console.log('then-Resolved.');
  7. });

  8. console.log('微任务');
复制代码


输出结果:
Snip20181210_81.png


上面代码中,Promise新建后立即执行,所以首先输出的是“promise-Promise”。

然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以“then-Resolved.”最后输出。

目前我们只需要了解到这个层级,深入了解请自行搜索哈。




实战

好了,知识点都有了,我们来个稍微难一点的例子。

代码:
  1. (function fishc(){
  2.     setTimeout(function(){console.log(4)},1111);

  3.     new Promise(function(resolve,reject){
  4.         console.log(1);
  5.         for(var i = 0 ; i < 88 ; i++){
  6.             if(i == 33) resolve();
  7.         }
  8.         console.log(2);
  9.     }).then(function () {
  10.         console.log(5);
  11.     });
  12.     console.log(3);
  13. })()
复制代码

Snip20181210_82.png


如果理解了上面的概念,看到上面的结果,应该很开心。

setTimeout,宏任务,存入宏任务队列,所以4不会立即执行。

Promise,函数本身是同步执行的,所以输出1

执行完Promise,输出2

then,异步执行,因此打印完1,2,then存入微任务中,然后打印3

第一次主线程执行完毕。

执行微任务中的回调函数,输出then中的5.

然后执行宏任务中的setTimeout,输出4

最后告诉一个本人总结的心法:
Snip20181211_2.png

只要主线程执行完毕,就立马执行setTimeout中的回调代码。



                               
登录/注册后可看大图








如果喜欢,别忘了评分


                               
登录/注册后可看大图


这位鱼油,如果喜欢本系列Js帖子,请订阅 专辑&#9758;传送门)(不喜欢更要订阅


本帖被以下淘专辑推荐:

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2019-1-10 17:39:38 | 显示全部楼层
同步、异步
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-11-15 19:25:41 | 显示全部楼层
不错,支持一下~
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2019-12-1 08:58:34 | 显示全部楼层
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2020-5-5 19:47:16 | 显示全部楼层
学习
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2020-5-11 10:41:49 | 显示全部楼层
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2020-6-19 21:12:10 | 显示全部楼层
感谢大佬分享~
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2020-8-4 16:17:53 | 显示全部楼层
1
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-1-1 23:11:08 | 显示全部楼层
6
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-4-22 19:37:31 | 显示全部楼层
q
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-5-18 18:38:02 | 显示全部楼层
0
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-6-29 00:46:34 | 显示全部楼层
牛逼克拉斯
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2021-12-19 20:54:48 | 显示全部楼层
学习
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-1-20 15:32:06 | 显示全部楼层
1
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-10-14 12:55:34 | 显示全部楼层
1
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-4-26 10:45

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表