前言

上次我们从高阶函数聊到了 promise ,这次我们聊聊:

•从 promise A+ 规范和 promise 应用来看 promise 的特性•promise 和 eventloop 的关系

从薛定谔的猫(Erwin Schrödinger's Cat)来理解 promise

薛定谔的猫是奥地利著名物理学家薛定谔提出的一个思想实验,那么这和 promise 有什么关系呢?在这个著名的实验中,假设在盒子里会有一只猫,然后我们打开盒子只会出现两个结果,猫死了或者是活着:

那么 promise 也类似,根据 promise A+ 规范[1] 当一个 promise 被创建出来以后,它就拥有三种可能状态 Pending (初始时为 pending)/ Fulfilled / Rejected 如果我们把范围放宽一点,那么 Fulfilled / Rejected 又可以被称为 Settled

okay,相信你已经理解了 promise 的三种状态,那细心同学看到上面有 then() 和 catch() 这样的方法可能不理解,我们再回到上面猫的例子里面,现在这个科学家比较变态,在第一次实验之后,猫出现了两种状态,但是他并没结束实验,而是针对这两种情况做了处理并继续了实验:

与之类似,一个完整的 promise ,在 Pending 状态发生变化时,只可能是两种情况,Fulfilled 和 Rejected,并且我们可以看到箭头是单向的,意味着这个过程是 不可逆 的。

这意味着,当 Pending 状态发生了变化,无论是变成 Fulfilled 还是 Rejected 都无法再改变了。

针对这两种情况,我们在 then() 里面可以传入两个回调函数 onFulfillment 和 onRejection 作为来处理不同的情况。

从图中我们可以看到,当 onFulfillment 时,我们通常会做一些异步的操作,而 onRejection 通常是做错误处理。然后我们把当前的 promise 重新返回,直到下次他的 then() 再次被执行。

一个promise.then().then().then() 这样的方式就是我们 上一篇文章[2] 中所说的 链式调用

通过 promise 的执行来看特性

通过上一节,我们已知 promise 本身的几个特性:

promise 有三种状态: Pending (初始时为 pending)/ Fulfilled /Rejected。•promise 状态的转变是不可逆的: Pending -> Fulfilled 或者 Pending -> Rejected 。•promise 支持 then() 的链式调用。

但是还有一些特性,我们需要从代码的角度来分析。

1. 创建后,立即执行

因为 promise 原意为承诺,也就是我预先承诺了将来要达成的一件事情。

所以有同学会认为必须等到承诺兑现,也就是 promise 的状态从 Pending 变为 Fulfilled 或者 Rejected 时,其构造函数接收的函数才会被执行。

但是实际上,一个 promise 被创建时,即使我们没有定义 then() ,其构造函数接收的函数也会立即执行:

let p = new Promise((resolve, reject) => {  console.log('A new promise was created1')  console.log('A new promise was created2')  console.log('A new promise was created3')  setTimeout(() => {    console.log('log setTimeout')  }, 3000)  resolve('success')})console.log('log outside')

输出结果:

A new promise was created1A new promise was created2A new promise was created3log outsidelog setTimeout

2. 异常处理的方式

根据 promise A+ 规范[3] , promise 的 then() 接收2个参数:

promise.then(onFulfilled, onRejected)

其中 onFulfilled 执行结束后调用,onRejected 拒绝执行后调用,看看这段代码:

let p = new Promise((resolve, reject) => {  reject('reject')  //throw 'error'})p.then(  data => {    console.log('1:', data)  },  reason => {    console.log('reason:', reason)  })

最后打印的是:

reason: reject

‍可以正常运行不是吗?但是我们发现实际应用中,我们并没有这样来定义 then() :

p.then(  data => {    console.log('1:', data)  },  reason => {    console.log('reason1:', reason)  }).then(  data => {    console.log('2:', data)  },  reason => {    console.log('reason2:', reason)  }).then(  data => {    console.log('3:', data)  },  reason => {    console.log('reason3:', reason)  })

而是使用 catch() 配合 onFulfilled() :

p.then(data => {  console.log('1:', data)}).then(data => {    console.log('2:', data)  }).then(data => {    console.log('3:', data)  }).catch(e => {      console.log('e2:', e)    })

表面上看,达到的效果是一样的,所以这样有什么好处呢?

•减少代码量。•在 onFulfilled() 中如果发生错误,也会进行捕获,不会中断代码的执行。

3. then() 是异步执行的

看一段代码:

let p = new Promise((resolve, reject) => {  console.log('A new promise was created1')  console.log('A new promise was created2')  console.log('A new promise was created3')  resolve('success')})console.log('log outside')p.then(data => {  console.log('then:', data)})

执行结果:

A new promise was created1A new promise was created2A new promise was created3log outsidethen: success

我们可以很清楚的看到,then() 中打印的内容是在最后的,为什么会这样呢?因为 p.then() 中传入的函数会被推入到 microtasks(异步任务队列的一种) 中,而任务队列都是在执行栈中的代码(同步任务)之后处理。

下面这些代码都在同步任务中处理:

console.log('A new promise was created1')console.log('A new promise was created2')console.log('A new promise was created3')console.log('log outside')

okay 看到这里你可能会有一些问题,例如:

•什么是 同步任务 ?•什么是 执行栈?•什么是 microtasks?•什么是 异步任务队列

要明白这些,就不得不聊聊 Event loop。

Event loop 是什么?为什么我们需要 Event loop?

在 W3C文档[4] 中我们可以找到关于它的描述:

To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. There are two kinds of event loops: those for browsing contexts, and those for workers.

翻译一下就是:

客户端必须使用本章节中所描述的事件循环,来协调事件,用户交互,脚本,呈现,网络等等。事件循环有两种:用于浏览上下文的事件循环和用于 worker 的事件循环。

我们写好一段 JavaScript 代码,然后浏览器打开这个页面,或者在 node 环境中运行它,就可以得到我们期望的结果,但是这段代码怎么执行的呢?

很多同学都知道,是 JavaScript 引擎在执行代码,而 JavaScript 引擎都是依托于一个宿主环境的,最通用的 JavaScript 宿主环境是浏览器。

这和 EventLoop 有什么关系呢?

因为宿主环境是浏览器,所以 JavaScript 引擎被设计为单线程。

为什么不能是多线程呢?举个例子:假如我们同时两个线程都操作同一个 DOM 元素,那应该如何处理呢?对吧。

okay,既然是单线程,意味着我们只能顺序执行代码,但是如果我们执行某一行特别耗费时间,是不是在这行后面的内容就被阻塞了呢?

所以我们需要在单线程的引擎中来实现异步,而 Event loop 就是实现异步的关键。

Event loop 中的任务队列 & 宏任务 & 微任务

首先当一段代码给到 JavaScript 引擎的时候,会区分这段代码是同步还是异步:

•同步的代码进入主线程执行•异步的代码加入到任务队列中,等待主线程通知执行

异步的代码加入到任务队列中,而任务队列又分为 宏任务队列(macro tasks) 和 微任务队列(micro tasks)

一个浏览器的上下文环境可能对应有多个宏任务队列但是只有一个微任务队列。你可能觉得会是这样:

但是实际上,每个宏任务都包含了一个微任务队列:

那么问题来了,我们怎么去判断这段代码要加入到宏任务队列,还是微任务队列中呢?

我们参考下文档[5] 中的解读:

Each task is defined as coming from a specific task source. All the tasks from one particular task source and destined to a particular event loop

每个任务都由特殊任务源来定义。来自同一个特殊任务源的所有任务都将发往特定事件循环

所以我们可以按照不同的来源进行分类,不同来源的任务都对应到不同的任务队列中

•(macro-task 宏任务)来源:I/OsetTimeout + setInterval + setImmediateUI renderder ···•(micro-task 微任务)来源:Promise ,process.nextTick ,MutationObserverObject.observe ···

明白了这些概念之后,我们来看看完整的执行过程。

Event loop 完整的执行过程

下图参考了 Philip Roberts的演讲[6] PPT同时加深和细化:

图的顺序从上往下看:

•代码开始执行,JavaScript 引擎对所有的代码进行区分。•同步代码被压入栈中,异步代码根据不同来源加入到宏任务队列尾部,或者微任务队列的尾部。•等待栈中的代码被执行完毕,此时通知任务队列,执行位于队列首部的宏任务。•宏任务执行完毕,开始执行其关联的微任务。•关联的微任务执行完毕,继续执行下一个宏任务,直到任务队列中所有宏任务被执行完毕。•执行下一个任务队列。

步骤 3 - 4 - 5 就是一个事件循环的基本原理。

最后

能够看到这里的都是真爱~

不知道这篇文章有没有让你充分理解呢?快来留下你的精彩评论吧~

右下角

表明你的态度

References

[1] promise A+ 规范: https://malcolmyu.github.io/2015/06/12/Promises-A-Plus/#%E6%89%A7%E8%A1%8C%E6%80%81%EF%BC%88Fulfilled%EF%BC%89
[2] 上一篇文章: https://juejin.im/post/5c91afdcf265da60fe7c2613
[3] promise A+ 规范: https://malcolmyu.github.io/2015/06/12/Promises-A-Plus/#%E6%89%A7%E8%A1%8C%E6%80%81%EF%BC%88Fulfilled%EF%BC%89
[4] W3C文档: https://www.w3.org/TR/html5/webappapis.html#event-loops
[5] 文档: 
[6] Philip Roberts的演讲: https://vimeo.com/96425312
[7] 你不知道的 Chrome 调试技巧: https://juejin.im/book/5c526902e51d4543805ef35e

关于奇舞周刊

《奇舞周刊》是360公司专业前端团队「奇舞团」运营的前端技术社区。关注公众号后,直接发送链接到后台即可给我们投稿。


从 薛定谔的猫 聊到 Event loop相关推荐

  1. [前端漫谈_4] 从 薛定谔的猫 聊到 Event loop

    前言 上次我们从高阶函数聊到了 promise ,这次我们聊聊: 从 promise A+ 规范和 promise 应用来看 promise 的特性 promise 和 eventloop 的关系 从 ...

  2. 《死亡搁浅》如何成了“薛定谔的猫”? 一个小岛秀夫式的乌托邦

    (写于TGA前夕) 在现在的风评里,死亡搁浅正处于"薛定谔的猫"的状态--既不是好,也不是烂,更不是中等,而是一半人说好,一半人说坏,没法定论是好是坏,就像盒子里的猫处于生与死的中 ...

  3. 86年后,终于有人完成「真人版」薛定谔的猫实验,量子纠缠了活体动物

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 来自:机器之心 首先要回答的问题:实验是量子的还是经典物理的? 你一定听说过薛定谔的猫 ...

  4. 薛定谔的猫——.NET 4.1 中的新基类,开源Preview中

    前言: 昨天一如既往地登上forums.asp.net答帖子,却被上面的一条滚动新闻雷到了: .NET 4.1 Preview - New Base Class Library (BCL) Exten ...

  5. 违背常识、颠覆认知,终于有人把薛定谔的猫讲明白了

    导读:在20世纪30年代中期,新兴量子理论的某些奇怪之处变得明显起来,薛定谔进行了一个思想实验,即"薛定谔的猫".他试图表明,量子理论数学一定是缺了些什么.他认为"猫不能 ...

  6. 当 Python 中混进一只薛定谔的猫……

    作者 | 豌豆花下猫 责编 | 胡巍巍 Python 是一门强大的动态语言,那动态体现在哪里,强大又体现在哪里呢? 除了好的方面,Python 的动态性是否还藏着一些使用陷阱呢,有没有办法识别与避免呢 ...

  7. 从“薛定谔的猫”联想到“好奇害死猫”

    喜欢物理学尤其是量子力学的朋友一定对薛定谔的猫不会陌生,至于那些不大懂的小伙伴建议可以网上搜索了解下,对你的人生观.价值观可能会有所改变(不说笑,真的哦). 对于量子论从爱因斯坦.波尔时代至今,一直是 ...

  8. 寻找薛定谔的猫:量子物理的奇异世界

    ▲长按"识别小程序"即可购买    编辑推荐 这只不死不活的猫总是像噩梦一样让物理学家们不得安宁--         全面论述了量子理论的基本概念,并赋予那些无限复杂又伤脑筋的实验 ...

  9. 你也可以看懂,量子力学的困惑,测不准原理 薛定谔的猫 !

    量子力学已经是现代物理学的基础学科之一,其影响力越来越大!巨大的影响力迫使着人们了解它,可量子世界中的种种奇异现象却挑战着常人的逻辑底线.甚至 让许多物理爱好者也摸不着头脑, 以至于玻尔(量子物理学家 ...

最新文章

  1. php addall,ThinkPHP3.2框架使用addAll()批量插入数据的方法
  2. 深度学习: mAP (Mean Average Precision)
  3. HBase与时空索引技术
  4. 第47讲:scrapy-redis分布式爬虫介绍
  5. SAP gateway 里对 OData eq ne lt gt 操作的实现源代码
  6. Android Studio 环境搭建参考,jdk10javac命令提示不是内部或外部命令
  7. C语言试题六十三之请编写函数fun:将s所指字符串中ascii值为偶数的字符删除,串中剩余字符形成一个新串放在t所指的数组中。
  8. c函数scanf(),printf()等常用格式字符串
  9. OpenGLES渲染
  10. Java面试题:Java设计模式11道常见面试题
  11. 搭建Hexo博客并部署到Github
  12. C语言自动处理异常,C语言中异常错误处理机制浅析
  13. uniapp引入阿里图标库
  14. 服务器被攻击被DDoS攻击该怎么办呢
  15. linux下的蓝牙驱动程序详解
  16. 网站优化怎样的外链能轻松收录,网站外链优化攻略
  17. 吴恩达新动向揭晓:加入精神健康领域的人工智能Woebot
  18. SD卡损坏及手动修复记录
  19. 白鹭(egret)序列帧图片打包动画资源
  20. 【知识兔】Excel教程:Index加Match组合计算阶梯提成

热门文章

  1. Selector的wakeup()
  2. 三维栅格地图构建之二:视差图及点云图
  3. 机器学习-------算法(七)
  4. https页面打不开
  5. 最常有用的英语口语900句
  6. windows 主题壁纸更换
  7. 一步步带你挑选机械键盘!
  8. torch.device()
  9. 计算机网络设计一个网络游戏,计算机网络编程课程设计-- 猜数游戏.doc
  10. Markdown 编辑器速查表