引入

var date = new Date() console.log(1, new Date() - date) setTimeout(() => {console.log(2, new Date() - date)
}, 500) Promise.resolve().then(console.log(3, new Date() - date)) while(new Date() - date < 1000) {} console.log(4, new Date() - date)

求上面的输出顺序和输出值,为什么?

答案:

1 0
3 1
4 1000
2 1000

其中,关于时间差结果可能因为计算机性能造成的微小差异,可忽略不计

你答对了吗?下面我们由浅入深探索本题

由浅入深探索 Promise 异步执行

首先,看一下 event loop 的基础必备内容

event loop 执行顺序:

首先执行 script 宏任务 执行同步任务,遇见微任务进入微任务队列,遇见宏任务进入宏任务队列
执行完当前宏任务所产生的微任务队列,有则依次执行,直到全部执行完,
执行浏览器 UI 线程的渲染工作 检查是否有WebWorker任务,有则执行
执行下一个宏任务,回到第二步,依此循环,直到宏任务和微任务队列都为空

微任务包括:MutationObserver、Promise.then()或catch()、Promise为基础开发的其它技术,比如fetch API、V8的垃圾回收过程、Node独有的process.nextTick 、 Object.observe(已废弃;Proxy 对象替代)

宏任务包括:script 、setTimeout、setInterval 、setImmediate 、I/O 、UI rendering 、 postMessage 、 MessageChannel

注意: 下面的题目都是执行在浏览器环境下

遇到不好理解的,可结合 【我文章里面的 promise原理实现】 进行理解,就很简单了

练习

1. 同步 + Promise

题目一:

var promise = new Promise((resolve, reject) => {console.log(1)resolve()console.log(2)
})
promise.then(()=>{console.log(3)
})
console.log(4)
// 1
// 2
// 4
// 3

首先明确, Promise 构造函数是同步执行的, then 方法是异步执行的
开始 new Promise ,执行构造函数同步代码,输出 1
再 resolve(), 将 promise 的状态改为了 resolved ,并且将 resolve 值保存下来,此处没有传值
执行构造函数同步代码,输出 2
跳出promise,往下执行,碰到 promise.then 这个微任务,将其加入微任务队列
执行同步代码,输出 4
此时宏任务执行完毕,开始检查微任务队列,执行 promise.then 微任务,输出 3

题目二:

var promise = new Promise((resolve, reject) => {console.log(1)
})
promise.then(()=>{console.log(2)
})
console.log(3)
// 1
// 3

开始 new Promise ,执行构造函数同步代码,输出 1
再 promise.then ,因为 promise中并没有resolve ,所以 then 方法不会执行
执行同步代码,输出 3

题目三:

var promise = new Promise((resolve, reject) => {console.log(1)
})
promise.then(console.log(2))
console.log(3)
// 1
// 2
// 3

首先明确, .then 或者 .catch 的参数期望是函数,传入非函数则会发生( value => value )
开始 new Promise ,执行构造函数同步代码,输出 1
然后 then() 的参数是一个 console.log(2) (注意:并不是一个函数),是立即执行的,输出 2
执行同步代码,输出 3

题目四:

Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log)
// 1

then(2) 、 then(Promise.resolve(3)) 发生了值穿透,直接执行最后一个 then ,输出 1

题目五:

var promise = new Promise((resolve, reject) => {console.log(1)resolve()reject()
})
promise.then(()=>{console.log(2)
}).catch(()=>{console.log(3)
})
console.log(4)
// 1
// 4
// 2

开始 new Promise ,执行构造函数同步代码,输出 1
再 resolve(), 将 promise 的状态改为了 resolved ,并且将 resolve 值保存下来,此处没有传值
再 reject() ,此时 promise 的状态已经改为了 resolved ,不能再重新翻转(状态转变只能是pending —> resolved 或者 pending —> rejected,状态转变不可逆)
跳出promise,往下执行,碰到 promise.then 这个微任务,将其加入微任务队列
往下执行,碰到 promise.catch 这个微任务,此时 promise 的状态为 resolved (非 rejected ),忽略 catch 方法
执行同步代码,输出 4
此时宏任务执行完毕,开始检查微任务队列,执行 promise.then 微任务,输出 2

题目六:

Promise.resolve(1).then(res => {console.log(res);return 2;}).catch(err => {return 3;}).then(res => {console.log(res);});
// 1
// 2

首先 resolve(1), 状态改为了 resolved ,并且将 resolve 值保存下来
执行 console.log(res) 输出 1
返回 return 2 实际上是包装成了 resolve(2)
状态为 resolved , catch 方法被忽略
最后 then ,输出 2

2. 同步 + Promise + setTimeout

题目一:

setTimeout(() => {console.log(1)
})
Promise.resolve().then(() => {console.log(2)
})
console.log(3)
// 3
// 2
// 1

首先 setTimout 被放入宏任务队列
再 Promise.resolve().then , then 方法被放入微任务队列
执行同步代码,输出 3
此时宏任务执行完毕,开始检查微任务队列,执行 then 微任务,输出 2
微任务队列执行完毕,检查执行一个宏任务
发现 setTimeout 宏任务,执行输出 1

题目二:

var promise = new Promise((resolve, reject) => {console.log(1)setTimeout(() => {console.log(2)resolve()}, 1000)
})promise.then(() => {console.log(3)
})
promise.then(() => {console.log(4)
})
console.log(5)
// 1
// 5
// 2
// 3
// 4

首先明确,当遇到 promise.then 时,如果当前的 Promise 还处于 pending 状态,我们并不能确定调用 resolved 还是 rejected ,只有等待 promise 的状态确定后,再做处理,所以我们需要把我们的两种情况的处理逻辑做成 callback 放入 promise 的回调数组内,当 promise 状态翻转为 resolved 时,才将之前的 promise.then 推入微任务队列
开始, Promise 构造函数同步执行,输出 1 ,执行 setTimeout
将 setTimeout 加入到宏任务队列中
然后,第一个 promise.then 放入 promise 的回调数组内
第二个 promise.then 放入 promise 的回调数组内
执行同步代码,输出 5
检查微任务队列,为空
检查宏任务队列,执行 setTimeout 宏任务,输入 2 ,执行 resolve , promise 状态翻转为 resolved ,将之前的 promise.then 推入微任务队列
setTimeout 宏任务出队,检查微任务队列
执行第一个微任务,输出 3
执行第二个微任务,输出 4

回到开头

现在看,本题就很简单了

var date = new Date() console.log(1, new Date() - date) setTimeout(() => {console.log(2, new Date() - date)
}, 500) Promise.resolve().then(console.log(3, new Date() - date)) while(new Date() - date < 1000) {} console.log(4, new Date() - date)

首先执行同步代码,输出 1 0
遇到 setTimeout ,定时 500ms 后执行,此时,将 setTimeout 交给异步线程,主线程继续执行下一步,异步线程执行 setTimeout
主线程执行 Promise.resolve().then , .then 的参数不是函数,发生( value => value ) ,输出 3 1
主线程继续执行同步任务 whlie ,等待 1000ms ,在此期间,setTimeout 定时 500ms 完成,异步线程将 setTimeout 回调事件放入宏任务队列中
继续执行下一步,输出 4 1000
检查微任务队列,为空
检查宏任务队列,执行 setTimeout 宏任务,输入 2 1000

总结

Promise 构造函数是同步执行的, then 方法是异步执行的

.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传

Promise的状态一经改变就不能再改变,构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用

.then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch是.then第二个参数的简便写法

当遇到 promise.then 时, 如果当前的 Promise 还处于 pending 状态,我们并不能确定调用 resolved 还是 rejected ,只有等待 promise 的状态确定后,再做处理,所以我们需要把我们的两种情况的处理逻辑做成 callback 放入 promise 的回调数组内,当 promise 状态翻转为 resolved 时,才将之前的 promise.then 推入微任务队列

哔哩哔哩:JS 异步笔试题相关推荐

  1. 哔哩哔哩2020校园招聘前端笔试题(卷一)

    1.下面哪几个和 [http://store.company.com/dir/page.html] 符合同源策略?( ) A. http://store.company.com/dir2/other. ...

  2. B站校园招聘后端笔试题(一)

    大家好,我是勾玉! 今天,我给大家带来了哔哩哔哩2020校园招聘后端笔试题,包括28道选择题和3道编程题,因为内容较多,我将分三期来讲,对每道题都给出答案以及解释~ 1.在Java中下面哪个对类的声明 ...

  3. k个一组翻转链表 哔哩哔哩2020校园招聘笔试题/LeetCode_25(困难)讲解

    目录标题 一.题目信息 二.解题思路 三.代码实现 四.其他精选题目分享 一.题目信息 LeetCode版本 牛客网版本 下面我以牛客网为例写代码 题目需要先输入一组数字代表链表的值,以#代表结尾. ...

  4. 一年中的第几天 哔哩哔哩2020校园招聘笔试题讲解

    一年中的第几天 一.题目信息 二.解题思路 三.代码实现 四.其他题目分享 一.题目信息 题目要求输入一个字符串,表示年月日,需要你输出改天是这一年的第几天.输入输入自己解决. 二.解题思路 假设我们 ...

  5. 这些大厂笔试题 你都见识(被无情鞭挞)过了吗?—— 哔哩哔哩篇

    题目链接: 哔哩哔哩2020校招 一.复数乘法 题目描述: 输入两个表示复数的字符串,输出它们相乘的结果的字符串 复数字符串用a+bi表示(a, b 为整数, i为虚数单位,i2=-1) 输入描述: ...

  6. JS 异步编程及常考面试题

    JS 异步编程及常考面试题 并发(concurrency)和并行(parallelism)区别 涉及面试题:并发与并行的区别? 异步和这小节的知识点其实并不是一个概念,但是这两个名词确实是很多人都常会 ...

  7. 笔试题:js实现螺旋式循环二维数组并输出一维数组

    笔试题:js实现螺旋式循环二维数组并输出一维数组 github链接: https://github.com/JiayiChen012/Written-questions. // 以下两种方式实现螺旋式 ...

  8. 哔哩哔哩软测工程师面试题

    哔哩哔哩软测工程师面试题 01. 测试计划工作的目的是什么?测试计划工作的内容都包括什么?其中哪些是最重要的? 软件测试计划是指导测试过程的纲领性文件,包含了产品概述.测试策略.测试方法.测试区域.测 ...

  9. 使用 JS 和GitHub Actions实现哔哩哔哩每日自动签到、投币、领取奖励

    使用 Axios 和GitHub Actions实现哔哩哔哩每日自动签到.投币.领取奖励 SCHEDULE-BILIBILI 是一个B站自动执行任务的工具,使用 JS + AXIOS 编写,通过它可以 ...

最新文章

  1. 激光雷达激烈竞争市场
  2. C++中static_cast/const_cast/dynamic_cast/reinterpret_cast的区别和使用
  3. 这可能是2018年IT界规模最大的裁员事件了
  4. linux搭建hdfs
  5. [Java网络编程基础]InetAddress的使用
  6. U-Time巡回完美收官:精细化数据将主导未来运营趋势(数据应用篇)
  7. proxysql on github
  8. python长度单位换算表_常用长度单位换算表
  9. Appium+python自动化(二十一)- 让猴子按你指令大闹手机,让我们都成为耍猴高手(超详解)...
  10. CNKI知网如何批量下载论文
  11. 显卡的优化以提高计算机性能作用,显卡优化,教您如何设置NVIDIA(英伟达)显卡玩游戏性能更高...
  12. 2021 中国开源码力榜启动,寻找开源世界的超级码丽
  13. linux系统中ssh命令,Linux系统中SSH 命令的用法有哪些?
  14. Distinct语法的使用
  15. String+CytoScape构建PPI网络
  16. Linux热点无网络,Linux网络配置:手提在连接WIFI热点情况下,Ubuntu16.04中怎样配置网络?...
  17. 709-50-2,methyl β-D-glucopyranoside,甲基β-D-吡喃葡萄糖苷半水合物
  18. ipad有必要用手写笔吗,哪个平板电脑触控笔最好用
  19. 上架应用宝多次被拒总结
  20. 使用极验【行为验证】实现人机交互验证

热门文章

  1. 字节小组长薪资被应届生员工倒挂7K,不把老员工当人?
  2. 强化学习和知识图谱实体对齐
  3. 关系数据库理论-范式
  4. SQL中的动态SQL
  5. 不吃苦,你要青春何用
  6. 百度年龄计算机在线,百度年龄计算器生日书app
  7. 视频教程-人工智能-知识图谱实战案例视频-NLP
  8. 战神引擎php充值,第十讲 战神引擎支付代码安装和数据库排序规则修改
  9. 济宁java平均工资,2019年济宁平均工资公布,济宁平均工资水平最新数据
  10. Excel行列互换怎么设置