此文首发于 https://lijing0906.github.io
前面的文章梳理了一下有关浏览器进程、事件循环机制、微任务和红任务、Promise的相关知识,这篇文章想讲讲有关这些知识的一些面试题。
参考文章
const 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
的创建是同步的,因此会先打印1
,遇到异步任务resolve()
就把resolve()
推入事件队列resolve()
之后的同步任务,所以打印2
4
3
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
const promise2 = promise1.then(() => {
throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
console.log('promise1', promise1)
console.log('promise2', promise2)
}, 2000)
// 执行结果如下:
// promise1 Promise { }
// promise2 Promise { }
// (node:50928) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: error!!!
// (node:50928) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
// promise1 Promise { 'success' }
// promise2 Promise {
// Error: error!!!
// at promise.then (...)
// at }
解读: promise
有 3 种状态:pending
、fulfilled
和 rejected
。状态改变只能是 pending->fulfilled
或者 pending->rejected
,状态一旦改变则不能再变。上面 promise2
并不是 promise1
,而是返回的一个新的 Promise
实例。
const promise = new Promise((resolve, reject) => {
resolve('success1')
reject('error')
resolve('success2')
})
promise.then((res) => {
console.log('then: ', res)
}).catch((err) => {
console.log('catch: ', err)
})
// 执行结果如下:
// then: success1
解读: Promise
的resolve
或 reject
只有第一次执行有效,多次调用没有任何作用,呼应代码二结论:promise
状态一旦改变则不能再变。
Promise.resolve(1).then((res) => {
console.log(res)
return 2
}).catch((err) => {
return 3
}).then((res) => {
console.log(res)
})
// 执行结果如下:
// 1
// 2
解读: promise 可以链式调用。提起链式调用我们通常会想到通过 return this 实现,不过 Promise 并不是这样实现的。promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用。
Promise中讲过Promise.resolve('success');
等价于new Promise(resolve => { resolve('success'); });
,因此先打印1
,然后return 2
,这个then
被调用之后返回一个Promise
,因此在第二个then
时会打印```2````。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('once')
resolve('success')
}, 1000)
})
const start = Date.now()
promise.then((res) => {
console.log(res, Date.now() - start)
})
promise.then((res) => {
console.log(res, Date.now() - start)
})
// 执行结果如下:
// once
// success 1002
// success 1003
解读: promise
的 .then
或者.catch
可以被调用多次,但这里 Promise
构造函数只执行一次。或者说 promise
内部状态一经改变,并且有了一个值,那么后续每次调用 .then
或者 .catch
都会直接拿到该值。Date.now() - start
的值看浏览器的执行效率,可能一样,可能相差一点点。
Promise.resolve().then(() => {
return new Error('error!!!')
}).then((res) => {
console.log('then: ', res)
}).catch((err) => {
console.log('catch: ', err)
})
// 执行结果如下:
// then: Error: error!!!
// at Promise.resolve.then (...)
// at ...
解读: .then
或者 .catch
中 return
一个 error
对象并不会抛出错误,所以不会被后续的 .catch
捕获,需要改成其中一种:
return Promise.reject(new Error('error!!!'))
throw new Error('error!!!')
因为返回任意一个非 promise
的值都会被包裹成 promise
对象,即 return new Error('error!!!')
等价于 return Promise.resolve(new Error('error!!!'))
。
const promise = Promise.resolve().then(() => {
return promise
})
promise.catch(console.error)
// 执行结果如下:
// TypeError: Chaining cycle detected for promise #
// at
// at process._tickCallback (internal/process/next_tick.js:188:7)
// at Function.Module.runMain (module.js:667:11)
// at startup (bootstrap_node.js:187:16)
// at bootstrap_node.js:607:3
解读: .then
或 .catch
返回的值不能是 promise
本身,否则会造成死循环。类似于:
process.nextTick(function tick () {
console.log('tick')
process.nextTick(tick)
})
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
// 执行结果如下:
// 1
解读: .then
或者 .catch
的参数期望是函数,传入非函数则会发生值穿透。
Promise.resolve().then(function success (res) {
throw new Error('error')
}, function fail1 (e) {
console.error('fail1: ', e)
}).catch(function fail2 (e) {
console.error('fail2: ', e)
})
// 执行结果如下:
// fail2: Error: error
// at success (...)
// at ...
解读: .then
可以接收两个参数,第一个是处理成功的函数,第二个是处理错误的函数。.catch
是 .then
第二个参数的简便写法,但是它们用法上有一点需要注意:.then
的第二个处理错误的函数捕获不了第一个处理成功的函数抛出的错误,而后续的 .catch
可以捕获之前的错误。当然以下代码也能达到同样的效果:
Promise.resolve().then(function success1 (res) {
throw new Error('error')
}, function fail1 (e) {
console.error('fail1: ', e)
}).then(function success2 (res) {
}, function fail2 (e) {
console.error('fail2: ', e)
})
process.nextTick(() => {
console.log('nextTick')
})
Promise.resolve().then(() => {
console.log('then')
})
setImmediate(() => {
console.log('setImmediate')
})
console.log('end')
// 执行结果如下:
// end
// nextTick
// then
// setImmediate
解读: process.nextTick
和 promise.then
都属于 microtask
,而 setImmediate
属于 macrotask
,在事件循环的 check
阶段执行。事件循环的每个阶段(macrotask
)之间都会执行 microtask
,事件循环的开始会先执行一次 microtask
。