看到过下面这样一道题:

(function test() {setTimeout(function() {console.log(4)}, 0);new Promise(function executor(resolve) { console.log(1); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(2); }).then(function() { console.log(5); }); console.log(3); })()

为什么输出结果是 1,2,3,5,4 而非 1,2,3,4,5 ?

比较难回答,但我们可以首先说一说可以从输出结果反推出的结论:

  1. Promise.then 是异步执行的,而创建Promise实例( executor )是同步执行的。
  2. setTimeout 的异步和 Promise.then 的异步看起来 “不太一样” ——至少是不在同一个队列中。

相关规范摘录

在解答问题前,我们必须先去了解相关的知识。(这部分相当枯燥,想看结论的同学可以跳到最后即可。)

Promise/A+ 规范

要想找到原因,最自然的做法就是去看规范。我们首先去看看 Promise的规范 。

摘录 promise.then 相关的部分如下:

promise.then(onFulfilled, onRejected)

2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].

Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.

规范要求, onFulfilled 必须在 执行上下文栈(execution context stack) 只包含 平台代码(platform code) 后才能执行。平台代码指 引擎,环境,Promise实现代码。实践上来说,这个要求保证了 onFulfilled 的异步执行(以全新的栈),在 then 被调用的这个事件循环之后。

规范的实现可以通过 macro-task 机制,比如 setTimeout 和 setImmediate ,或者 micro-task 机制,比如 MutationObserver 或者 process.nextTick 。因为promise的实现被认为是平台代码,所以可以自己包涵一个 task-scheduling 队列或者 trampoline 。

通过对规范的翻译和解读,我们可以确定的是 promise.then 是异步的,但它的实现又是平台相关的。要继续解答我们的疑问,必须理解下面几个概念:

  1. Event Loop,应该算是一个前置的概念,理解它才能理解浏览器的异步工作流程。
  2. macro-task 机制和 micro-task 机制,这组概念很新,之前根本没听过,但却是解决问题的核心。

Event Loop 规范

HTML5 规范里有 Event loops 这一章节(读起来比较晦涩,只关注相关部分即可)。

  1. 每个浏览器环境,至多有一个event loop。
  2. 一个event loop可以有1个或多个task queue。
  3. 一个task queue是一列有序的task,用来做以下工作: Events task, Parsing task, Callbacks task, Using a resource task, Reacting to DOM manipulation task等。

每个task都有自己相关的document,比如一个task在某个element的上下文中进入队列,那么它的document就是这个element的document。

每个task定义时都有一个task source,从同一个task source来的task必须放到同一个task queue,从不同源来的则被添加到不同队列。

每个(task source对应的)task queue都保证自己队列的先进先出的执行顺序,但event loop的每个turn,是由浏览器决定从哪个task source挑选task。这允许浏览器为不同的task source设置不同的优先级,比如为用户交互设置更高优先级来使用户感觉流畅。

Jobs and Job Queues 规范

本来应该接着上面Event Loop的话题继续深入,讲macro-task和micro-task,但先不急,我们跳到 ES2015 规范,看看 Jobs and Job Queues 这一新增的概念,它有点类似于上面提到的 task queue 。

一个 Job Queue 是一个先进先出的队列。一个ECMAScript实现必须至少包含以下两个 Job Queue :

Name Purpose
ScriptJobs Jobs that validate and evaluate ECMAScript Script and Module source text. See clauses 10 and 15.
PromiseJobs Jobs that are responses to the settlement of a Promise (see 25.4).

单个 Job Queue 中的PendingJob总是按序(先进先出)执行,但多个 Job Queue 可能会交错执行。

跟随PromiseJobs到25.4章节,可以看到 PerformPromiseThen ( promise, onFulfilled, onRejected, resultCapability ) :

这里我们看到, promise.then 的执行其实是向 PromiseJobs 添加Job。

event loop怎么处理tasks和microtasks?

好了,现在可以让我们真正来深入task(macro-task)和micro-task。

认真说,规范并没有包括macro-task 和 micro-task这部分概念的描述,但阅读一些大神的博文以及从规范相关概念推测,以下所提到的在我看来,是合理的解释。但是请看文章的同学辩证和批判地看。

首先, micro-task在ES2015规范中称为Job。 其次,macro-task代指task。

哇,所以我们可以结合前面的规范,来讲一讲Event Loop(事件循环)是怎么来处理task和microtask的了。

  1. 每个线程有自己的事件循环,所以每个web worker有自己的,所以它才可以独立执行。然而,所有同属一个origin的windows共享一个事件循环,所以它们可以同步交流。
  2. 事件循环不间断在跑,执行任何进入队列的task。
  3. 一个事件循环可以有多个task source,每个task source保证自己的任务列表的执行顺序,但由浏览器在(事件循环的)每轮中挑选某个task source的task。
  4. tasks are scheduled,所以浏览器可以从内部到JS/DOM,保证动作按序发生。在tasks之间,浏览器可能会render updates。从鼠标点击到事件回调需要schedule task,解析html,setTimeout这些都需要。
  5. microtasks are scheduled,经常是为需要直接在当前脚本执行完后立即发生的事,比如async某些动作但不必承担新开task的弊端。microtask queue在回调之后执行,只要没有其它JS在执行中,并且在每个task的结尾。microtask中添加的microtask也被添加到microtask queue的末尾并处理。microtask包括 mutation observer callbacks 和 promise callbacks 。

结论

定位到开头的题目,流程如下:

  1. 当前task运行,执行代码。首先 setTimeout 的callback被添加到tasks queue中;
  2. 实例化promise,输出 1 ; promise resolved;输出 2 ;
  3. promise.then 的callback被添加到microtasks queue中;
  4. 输出 3 ;
  5. 已到当前task的end,执行microtasks,输出 5 ;
  6. 执行下一个task,输出 4 。

转载于:https://www.cnblogs.com/yzhihao/p/9383822.html

Promise和setTimeout执行顺序 面试题相关推荐

  1. 【异步系列二】Promise原理及执行顺序详解

    前言 Promise 是 javascript 中非常重要的一环,熟悉它是必须的,而且在面试中也常常会问到相关面试题. 在了解 Promise 之前,需要了解什么是异步编程,可以参考我的一篇文章:Ja ...

  2. 一道异常处理执行顺序面试题的简单分析

    异常处理,我们写的代码里经常会用到:try{}catch{}finally{}.可是大家真的了解它吗? 下面的代码,运行结果是什么?大家猜一下: View Code static class Prog ...

  3. 解决闭包问题时 setTimeout执行顺序提前的问题

    之前看视频的时候学到闭包问题,想自己试试就写了一下 var arr = [1,2,3,4,5,6,7,8,9]; for(let i = 0 ; i < arr.length ; i ++ ){ ...

  4. 面试题--promise和setTimeout的输出顺序

    面试题–promise和setTimeout的输出顺序 下面一段代码是在网上看到的一段关于promise和setTimeout的输出顺序的代码,下面做一下解答,有兴趣的百度自行了解js的执行机制(包括 ...

  5. 8张图让你一步步看清 async/await 和 promise 的执行顺序

    2019独角兽企业重金招聘Python工程师标准>>> **摘要:**面试必问 原文:8张图帮你一步步看清 async/await 和 promise 的执行顺序 作者:ziwei3 ...

  6. JS 中关于Promise的用法,状态,执行顺序详解,面试可用(原创)

    前言 在实际项目中我们常会去用已经封装好的promise如axios,或者也会自己去封装promise,甚至在面试中,关于promise的面试题也层出不穷,promise的重要性不言而喻,故写该文章记 ...

  7. 前端碎碎念 之 nextTick, setTimeout 以及 setImmediate 三者的执行顺序

    『前端碎碎念』系列会记录我平时看书或者看文章遇到的问题,一般都是比较基础但是容易遗忘的知识点,你也可能会在面试中碰到. 我会查阅一些资料并可能加上自己的理解,来记录这些问题.更多文章请前往我的个人博客 ...

  8. 详解promise、async和await的执行顺序

    说明: 本文摘自 详解 promise.async和await的执行顺序. 1.题目和答案 一道题题目:下面这段promise.async和await代码,请问控制台打印的顺序? async func ...

  9. Promise.all执行顺序

    Promise.all执行顺序 理解 栗子 理解 Promise.all等待所有Promise执行完毕之后,按照放在all中的先后顺序将resolve()返回的数据放在Promise.all的reso ...

最新文章

  1. 20145236《网络攻防》Exp4 恶意代码分析
  2. 算法基础知识科普:8大搜索算法之二分搜索
  3. 一个小小的AI训练营竟然卧虎藏龙
  4. ***产业链 安全新忧患
  5. SQL中LIKE的妙用
  6. MySQL中数据库的操作
  7. Ubuntu14.04 YouCompleteMe Configure
  8. 解决strings: '/lib/libc.so.6': No such file
  9. linux qt 音频文件怎么打开,Qt:获取Linux中可用音频设备的列表
  10. 轻松了解面试官心理!ElasticSearch写入数据的工作原理是什么? | 技术头条
  11. 【大数据】学习大数据前需要掌握的知识
  12. 计算机组成原理二进制地址码,计算机组成原理第四章第三讲.ppt
  13. python极客项目编程 豆瓣_《Python极客项目编程》
  14. Java类与对象——几个课堂例子的总结及作业
  15. shell 脚本实战 四
  16. java源代码审计报告_审计档案管理系统 - WEB源码|JSP源码/Java|源代码 - 源码中国...
  17. 记录:【DIY】一块SSD拯救了我的笔记本
  18. 南京大学计算机考研难度分析,2020考研难度高的院校分析之【南京大学】
  19. 微信小程序 RangeError: WebAssembly.Memory(): could not allocate memory
  20. WIN10家庭版增加本地安全策略

热门文章

  1. spss分析qpcr数据_SPSS 数据分析,掌握这 6 大模块就够了!
  2. linux 进程装入 物理内存 页表,linux进程空间一步步探究.doc
  3. curl进行post请求的demo
  4. nginx多站点配置,以及隐藏index.php
  5. 【操作系统】考研の内存管理方法(看不懂你来打我~!)
  6. 【Web安全】PHP与Web表单交互-POST方法与GET方法(看不懂你来打我)
  7. python【蓝桥杯vip练习题库】ALGO-231多阶乘计算
  8. .net 同步mysql_MySQL服务器主从数据库同步配置
  9. 企业网络推广专员浅析企业网络推广初期网站优化应重视的一些问题
  10. 网站标题怎么写可以被搜索引擎快速识别?