讨论/技术交流/分享|「前端面试」 Promise 面试题,你能做对几道/
分享|「前端面试」 Promise 面试题,你能做对几道

promise.png

知识点

  • 宏任务 / 微任务
  • event loop(事件循环)
  • 异步函数

题目一

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,2
  • 执行同步代码,输出 4
  • 执行 promise.then() 中的代码,输出 3

题目二

console.log('script start');
setTimeout(function(){
    console.log('setTimeout');
},0);
new Promise(function(resolve){
    console.log('promise1');
    resolve();
    console.log('promise2');
}).then(function(){
    console.log('promise then');
});
console.log('script end');

答案

script start
promise1
promise2
script end
promise then
setTimeout

解读

  • 首先 JavaScript 引擎会执行一个宏任务,注意这个宏任务一般是指主干代码本身,也就是目前的同步代码
  • 执行过程中如果遇到微任务,就把它添加到微任务任务队列中
  • 宏任务执行完成后,立即执行当前微任务队列中的微任务,直到微任务队列被清空
  • 微任务执行完成后,开始执行下一个宏任务
  • 如此循环往复,直到宏任务和微任务被清空

题目三

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 的单向传递的特点,promise 有三种状态:

  • padding
  • resolved
  • rejected

这三种状态的转移只能是 padding -> resolvedpadding -> rejected。不会出现 resolved -> rejected 或者 padding -> resolved -> rejected 等等,即状态只能从 padding 单向传递,且只能改变一次。

在 promise 中,resolve() 将 promise 的状态变为 resolved成功时调用,resolved 状态在先,先调用异步函数 promise.then(('success1') => { console.log('then:', 'success1'); })

promise 的状态一旦改变就不能修改了,因此 .catch() 和第二个 resolve('success2') 不会执行。

解读

题目四

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

答案

1

解读

.then() 中的参数必须是函数,如果是非函数,则会发生值穿透,最终执行第三个 .then() 函数,参数为 1

题目五

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

答案

1
2

解读

  • promise.resolve() 返回一个新的 promise,执行第一个 .then(),参数为 1
  • 打印 1,return 返回一个新的 promise,参数为 2
  • 执行第二个 .then(),参数为 2,并打印出 2

题目六

Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })

答案

then:  Error: error!!!

解读

这里的陷阱在于只有抛出错误时才会执行 .catch(),题目中并不是抛出错误,所以执行 .then()

改成如下方式会执行 .catch():

  • return Promise.reject(new Error('error!!!'))
  • throw new Error('error!!!')

题目七

const promise = Promise.resolve()
    .then(() => {
        return promise
    })
promise.catch(console.error)

答案

程序报错:

TypeError: Chaining cycle detected for promise #<Promise>

解读

.then().catch() 返回的值不能是 promise 本身,否则会造成死循环。

题目八

setTimeout(() => {
    console.log(1)
    Promise.resolve(3).then(data => console.log(data))
}, 0)

setTimeout(() => {
    console.log(2)
}, 0)

答案

1
3
2

解读

  • 先执行整体代码,没有发现微任务;
  • 再执行下一个宏任务—setTimeout回调函数,打印出1;
  • 遇到微任务—Promise 回调函数;
  • 立即执行微任务,打印出3;
  • 微任务队列被清空,执行下一个宏任务—下一个setTimeout回调函数,打印出2。

这道题有点不一样的是,Promise 回调这个微任务,是在 setTimeout 回调这个宏任务中,被触发的。不过无论怎样,它们都遵循 Event Loop 的模型规则。

题目九

var pro = new Promise((res,rej)=>{
    res(1);
});
pro.then(res=>{
    console.log(res);
});
setTimeout(()=>{
console.log(2);
})
pro.then(res=>{
    console.log(res);
})

答案

1
1
2

解读

promise 对象赋值给了变量 pro,每个 .then() 都是独立的。

题目十

async function async1 () {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

async function async2 () {
    console.log('async2');
}

console.log('script start');

setTimeout(function () {
    console.log('setTimeout');
}, 0);

async1() 

new Promise (function (resolve) {
    console.log('promise1');
    resolve();
}).then (function () {
    console.log('promise2');
})

console.log('script end');

答案

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

解读

注意以下几点:

  • 定义 async1()async2(),不会立即执行
  • await async2(); 表示先执行 async2(),然后将 await 后面的代码当作 .then() 中的回调来处理
  • 初始化 promise,promise 中的代码会立即执行

总结

基本上所有与异步相关的问题都可以用 event loop 来解释。最后,我们再来回顾一遍 event loop 的执行规则:

  • 首先执行宏任务,这个宏任务一般指 主干代码,我们经常称之为同步代码;
  • 执行过程中如果遇到了微任务,将其加入到微任务队列中;
  • 执行完宏任务之后,开始执行微任务队列中的任务,直至微任务队列为空;
  • 微任务执行完之后,开始执行下一个宏任务;
  • 如何循环执行,直到宏任务和微任务都执行完。

(个人疏漏之处在所难免,如有问题欢迎指出哦😊)


参考一

参考二

参考三

29

哈哈,我还想纠正下来着,结果发现有伙伴已经提出来了。whatever,感谢楼主分享。

展开全部 4 讨论