Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第172题:JS 异步笔试题,请写出下面代码的运行结果(哔哩哔哩) #471

Open
sisterAn opened this issue Apr 13, 2021 · 6 comments

Comments

@sisterAn
Copy link
Collaborator

sisterAn commented Apr 13, 2021

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)

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

@117sparta
Copy link

117sparta commented Apr 14, 2021

console.log(1, new Date() - date);
console.log(3, new Date() - date);
console.log(4, new Date() - date);
console.log(2, new Date() - date);

当前宏任务 -> 微任务 -> 下一个宏任务。

Promise.resolve().then(console.log(3, new Date() - date)) 这行有个坑,差点被骗了

@impguier
Copy link

var date = new Date()
// 直接输出1
console.log(1, new Date() - date)
// 加入宏任务队列 h1
setTimeout(() => {
console.log(2, new Date() - date)
}, 500)
// Promise.resovle()同步执行,状态变更, .then加入微任务中一个任务, m1,console.log(3, new Date() - date) 是一个表达式直接执行了
Promise.resolve().then(console.log(3, new Date() - date))
// 同步阻塞
while(new Date() - date < 1000) {}
// 输出4
console.log(4, new Date() - date)
// 1
//3
//4
// 主栈执行完毕, 出栈, 再找宏任务, h1执行,输出2
//2

如有不对,多多指教~

@shenzhenSk
Copy link

一眼扫过去,还是被Promise.resolve().then(console.log(3, new Date() - date))骗了
Promise.resolve().then(function (){console.log(3, new Date() - date)})
这样的话就没人会做错了吧

@senfish
Copy link

senfish commented Apr 15, 2021

执行结果: 1 3 4 2。
代码从上往下执行,
先打印1,看见setTimeout丢到宏任务里面,等待执行,
因为promise.then()的参数是一个console.log(注意:并不是一个函数),且then是立即执行的。
函数立即执行,会先走参数的逻辑,然后在去调用函数。
所以先打印3,并且给then传了一个undefinedconsole.log的返回值是undefined),再把then丢到微任务里面
while循环是同步任务,等待1s后打印4,
此时同步任务走完了,开始执行异步任务,先将then取出来执行,发现then的第一个参数是一个undefined
promise内部会判断,如果then的第一个参数,也就是成功回调函数,不是一个参数的话,会自动给他包装成一个函数,并且将resolvevalue值透传到下一个then里面。
然后去执行setTimeout,最后打印2。
如果说得不对的地方,欢迎大家讨论

@sisterAn
Copy link
Collaborator Author

sisterAn commented Apr 15, 2021

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 线程的渲染工作
  • 检查是否有Web Worker任务,有则执行
  • 执行下一个宏任务,回到第二步,依此循环,直到宏任务和微任务队列都为空

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

宏任务包括scriptsetTimeoutsetIntervalsetImmediateI/OUI renderingpostMessageMessageChannel

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

遇到不好理解的,可结合 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)
  • 状态为 resolvedcatch 方法被忽略
  • 最后 then ,输出 2

2. 同步 + Promise + setTimeout

题目一:

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

解析:

  • 首先 setTimout 被放入宏任务队列
  • Promise.resolve().thenthen 方法被放入微任务队列
  • 执行同步代码,输出 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 ,执行 resolvepromise 状态翻转为 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 推入微任务队列

原文

@Yangfan2016
Copy link

这道题 考察的 两个地方

  1. then 的回调参数
    Promise.resolve().then(console.log(3, new Date() - date))
  2. 主线程 和 异步任务
    while(new Date() - date < 1000) {}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants