Promise面试题

目录

准备

第一题

第二题

第三题

第四题

第五题

第六题

第七题

第八题

第九题

第十题

第十一题

第十二题

第十三题

第十四题

第十五题

第十六题

第十七题

第十八题

第十九题

第二十题


准备

在做这类题目之前,你应该清楚JS中的事件运行机制,才能够比较好地解决这类题目。JS是单线程的,为了解决单线程运行阻塞问题,JavaScript用到了计算机系统的一种运行机制,这种机制就叫做事件循环(Event Loop)。代码执行的顺序时先执行同步事件,同步事件执行完毕之后,在进行事件循环中执行列队中的事件,事件循环主要包括:微任务以及宏任务。常见的微任务比如:promise.then、async/await、process、nextTick等,宏任务主要有:定时器相关的以及ajax请求等。同步的任务都执行完了,才会执行事件循环的内容进行事件,要执行宏任务的前提是清空了所有的微任务。

第一题

下面两段代码分别输出什么?

setTimeout(() => {
  console.log('timer1');
  setTimeout(() => {
    console.log('timer3')
  }, 0)
}, 0)
setTimeout(() => {
  console.log('timer2')
}, 0)
console.log('start')
setTimeout(() => {
  console.log('timer1');
  Promise.resolve().then(() => {
    console.log('promise')
  })
}, 0)
setTimeout(() => {
  console.log('timer2')
}, 0)
console.log('start')

输出

第一段代码:start timer1 timer2 timer3

第二段代码:start timer1 promise timer2

第一段代码首先将第一个定时器压入到宏任务的队列中,然后接着将第二个定时器压入宏任务中。然后先执行完同步事件,最下面console.log中的内容,然后再处理宏任务队列中的事件,宏任务的执行顺序先执行先压入队列中的定时器,于是打印出timer1,接着又遇到一个定时器,将该定时器压入到宏任务的队列中。接着继续执行队列中的宏任务,依次打印出timer2以及timer3。最终的输出顺序为:start 、timer1、timer2、timer3

第二段代码首先将第一个定时器压入到宏任务队列中,接着压入第二个定时器,执行同步代码打印出start。同步事件执行完毕之后进入到宏任务队列,首先输出timer1,接着遇到promise.then为微任务,因此将其压入到微任务的队列中。目前宏任务队列中有一个事件,微任务队列中也有一个事件。于是先执行微任务中的事件,输出promise再输出timer2。

第二题

下面代码输出什么?

const promise = new Promise((resolve, reject) => {
  reject("error");
  resolve("success2");
});
promise
.then(res => {
    console.log("then1: ", res);
  }).then(res => {
    console.log("then2: ", res);
  }).catch(err => {
    console.log("catch: ", err);
  }).then(res => {
    console.log("then3: ", res);
  })

输出:

"catch: " "error"

"then3: " undefined

首先我们应该知道promise实例的状态只能改变一次,因此只会执行reject这一行的代码,resolve不会执行,因此实例的状态为失败。状态改变之后会执行失败的回调,catch不管被连接到哪里,都能捕获上层未捕捉过的错误。因此首先输出catch,error。然后catch方法返回的也是一个promise对象,由于它没有return值,因此为一个值为undefined,成功状态的promise对象。因此后续then方法中的回调会执行,输出undefined。

第三题

下面代码输出什么?

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

输出:

1  2

resolve为Promise构造函数本身的方法,当传入的参数不是一个promise对象时,返回的都是一个成功状态的promise对象,同时对应的值为其参数的值。若参数传入的是一个promise对象时,则其返回的promise对象的状态由传入的promise对象的状态决定。

本题目中传入1,则会返回一个成功状态且值为1的promise对象,会执行then方法中成功的回调函数,于是打印出1,接着这个then同样返回一个promise对象,由于是返回数字,因此该对象为成功状态值为2的promise对象。于是执行后续的then方法,输出2。

第四题

下面代码输出什么?

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('timer')
    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)
})

输出:

'timer'
'success' 1001
'success' 1002

首先将定时器压入到宏任务队列中,然后接着是两个then的回调,但是两个then的回调只会等到promise对象的状态改变时才会执行,相当于下面两个then的回调不存在。注意不会压入道队列中,需要执行对应的代码才会压入到队列中。于是执行定时器中的代码,先输出timer,接着执行resolve,于是promise实例的状态变成了成功,于是执行两个then方法,先执行第一个,输出值为success, 时间为1秒之后,因此为1001,接着执行第二个then的回调,同样输出success,输出的时间为1002。如果执行足够快的话,也可能两个都是1001。

第五题

下面代码输出什么?

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

输出:

"then: " "Error: error!!!"

上面我们已经有讨论过了resolve方法返回的promise对象的情况。由于它返回的不是一个promise对象,因此这里的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!'))。执行then的回调。

第六题

下面代码输出什么?

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

输出:

Uncaught (in promise) TypeError: Chaining cycle detected for promise #

.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环,因此结果会报错。

第七题

下面代码输出什么?

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

输出:

1

.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。第一个then和第二个then中传入的都不是函数,一个是数字类型,一个是对象类型,因此发生了透传,将resolve(1) 的值直接传到最后一个then里。

第八题

下面代码输出什么?

Promise.resolve()
  .then(function success (res) {
    throw new Error('error!!!')
  }, function fail1 (err) {
    console.log('fail1', err)
  }).catch(function fail2 (err) {
    console.log('fail2', err)
  })

输出:

fail2 Error: error!!!

由于Promise调用的是resolve(),没有传入参数返回的是一个成功状态值为undefined的promise对象。因此.then()执行的应该是success()函数,可是success()函数抛出的是一个错误,它会被后面的catch()给捕获到,而不是被fail1函数捕获。

第九题

下面代码输出什么?

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

输出:

'async1 start'
'async2'
'start'
'async1 end'

async函数中的代码同步执行,首先输出async1 start,接着执行await之后的第二个async2函数。await之后的代码会等到所有的同步任务完成之后在执行,所有先执行start之后再输出async1 end。

第十题

下面代码输出什么?

async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')

输出:

'script start'
'async1 start'
'promise1'
'script end'

在async1中await后面的Promise是没有返回值的,也就是它的状态始终是pending状态,因此相当于一直在await,await,await却始终没有响应...所以在await之后的内容是不会执行的,也包括async1后面的 .then。

第十一题

下面代码输出什么?

async function async1 () {
  console.log('async1 start');
  await new Promise(resolve => {
    console.log('promise1')
    resolve('promise resolve')
  })
  console.log('async1 success');
  return 'async1 end'
}
console.log('srcipt start')
async1().then(res => {
  console.log(res)
})
new Promise(resolve => {
  console.log('promise2')
  setTimeout(() => {
    console.log('timer')
  })
})

输出:

'script start'
'async1 start'
'promise1'
'promise2'
'async1 success'
'async1 end'
'timer'

async1函数声明了,但是还没有执行,所有先输出下面的打印语句,srcipt start。截止调用async1函数,执行内部的代码,输出async1 start,执行await中的代码,输出promise1,之后的代码需要所有同步事件完成之后再执行。于是到下面的构造函数,输出promise2,遇到定时器将其压入到宏任务的队列中,接着执行await之后的代码输出async1 success,同时async1函数执行完毕返回一个成功状态的promise对象,对应的值为async1 end,并执行then的回调函数进行输出。

第十二题

下面代码输出什么?

async function testSometing() {
  console.log("执行testSometing");
  return "testSometing";
}

async function testAsync() {
  console.log("执行testAsync");
  return Promise.resolve("hello async");
}

async function test() {
  console.log("test start...");
  const v1 = await testSometing();
  console.log(v1);
  const v2 = await testAsync();
  console.log(v2);
  console.log(v1, v2);
}

test();

var promise = new Promise(resolve => {
  console.log("promise start...");
  resolve("promise");
});
promise.then(val => console.log(val));

console.log("test end...");

输出:

'test start...'
'执行testSometing'
'promise start...'
'test end...'
'testSometing'
'执行testAsync'
'promise'
'hello async'
'testSometing' 'hello async'

首先我们需要知道await右侧的表达式一般为promise对象,await返回的是promise成功的值,await的promise失败了,就会抛出异常,需要通过try...catch捕获处理。知道了这个知识点,那我们就知道v1以及v2的输出值了,为promise对象成功的值。而遇到await,后续的代码需要等到同步执行完毕之后才能够执行。套路是跟上面一样的。知识代码稍微绕了一些。

第十三题

下面代码输出什么?

async function async1 () {
  await async2();
  console.log('async1');
  return 'async1 success'
}
async function async2 () {
  return new Promise((resolve, reject) => {
    console.log('async2')
    reject('error')
  })
}
async1().then(res => console.log(res))

输出:

'async2'
Uncaught (in promise) error

我们上一题已经讲到了await后面接收到的promise对象的状态为失败,则它会抛出错误。则会输出async2,抛出错误之后就不会继续执行了。若想要继续执行下去的话应该使用try...catch来进行捕获错误,则会继续执行下去,对应的代码如下:

  async function async1() {
            try{
                await async2();
            }catch(e){
                console.log(e)
            } 
            console.log('async1');
            return 'async1 success'
        }
        async function async2() {
            return new Promise((resolve, reject) => {
                console.log('async2')
                reject('error')
            })
        }
        async1().then(res => console.log(res))

第十四题

下面代码输出什么?

const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
            console.log(p)
        }, 0)
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });
}));
first().then((arg) => {
    console.log(arg);
});
console.log(4);

输出:

3
7
4
1
2
5
Promise{: 1}

首先调用frist函数,输出3,7,将定时器压入队列中,resolve(1),将p的then回调压入队列中,resolve(2)将first的then回调压入到队列中。然后输出同步代码4。接下来去微任务队列中找,第一个输出1,第二个输出2。接下来去宏任务队列,输出5,p实例对象已经改变,resolve(6)没有效果。最后输出p。为成功状态值为1的promise对象。

第十五题

下面代码输出什么?

const async1 = async () => {
  console.log('async1');
  setTimeout(() => {
    console.log('timer1')
  }, 2000)
  await new Promise(resolve => {
    console.log('promise1')
  })
  console.log('async1 end')
  return 'async1 success'
} 
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .catch(4)
  .then(res => console.log(res))
setTimeout(() => {
  console.log('timer2')
}, 1000)

输出:

'script start'
'async1'
'promise1'
'script end'
1
'timer2'
'timer1'

这道题目的核心关键点在于await之后的promise对象,若没有返回值,则会一直处于await的状态,后面的代码不会在执行了,且async.then方法也不会执行。promise中的resolve方法之后携带的then回调前面也有讲过会发送值穿透的问题。若这两点能够理解这道题的答案也就出来了。

第十六题

下面代码输出什么?

const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve('resolve3');
    console.log('timer1')
  }, 0)
  resolve('resovle1');
  resolve('resolve2');
}).then(res => {
  console.log(res)
  setTimeout(() => {
    console.log(p1)
  }, 1000)
}).finally(res => {
  console.log('finally', res)
})

输出:

'resolve1'
'finally' undefined
'timer1'
Promise{: undefined}

finally的回调函数也是一个微任务,因此它也会被压入到微任务的队列中。finally不管Promise的状态是resolved还是rejected都会执行,且它的回调函数是接收不到Promise的结果的。最后一个定时器打印出的p1其实是.finally的返回值,我们知道.finally的返回值如果在没有抛出错误的情况下默认会是上一个Promise的返回值,而这道题中.finally上一个Promise是.then()但是这个.then()并没有返回值,所以p1打印出来的Promise的值会是undefined。

也可以这么理解,在 Promise 中,状态的确定和最终的结果值是两个不同的概念。状态的确定是通过 resolve 或 reject 来实现的,而结果值则是由回调函数的返回值决定的。

第十七题

下面代码输出什么?

Promise.resolve('1')
  .then(res => {
    console.log(res)
  })
  .finally(() => {
    console.log('finally')
  })
Promise.resolve('2')
  .finally(() => {
    console.log('finally2')
  	return '我是finally2返回的值'
  })
  .then(res => {
    console.log('finally2后面的then函数', res)
  })

输出:

'1'
'finally2'
'finally'
'finally2后面的then函数' '2'

finally()方法不管Promise对象最后的状态如何都会执行,.finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是没法知道Promise最终的状态是resolved还是rejected的。它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。上面的代码中,这两个Promise的.finally都会执行,且就算finally2返回了新的值,它后面的then()函数接收到的结果却还是'2'。

第十八题

下面代码输出什么?

function promise1 () {
  let p = new Promise((resolve) => {
    console.log('promise1');
    resolve('1')
  })
  return p;
}
function promise2 () {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}
promise1()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally1'))

promise2()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally2'))

输出:

'promise1'
'1'
'error'
'finally1'
'finally2'

这道题需要注意,then后面的finally或者是catch后面的finally,在then或者catch微任务还没有执行时是不会执行的。当then或者catch执行了才会将后面的finally加入到微任务的队列中。代码并不会接着往链式调用的下面走,也就是不会先将.finally加入微任务列表,那是因为.then本身就是一个微任务,它链式后面的内容必须得等当前这个微任务执行完才会执行,因此这里我们先不管.finally()。其他的没啥问题。

第十九题

下面代码输出什么?

function runAsync (x) {
  const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
  return p
}
function runReject (x) {
  const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))
  return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
  .then(res => console.log(res))
  .catch(err => console.log(err))

输出:

// 1s后输出
1
3
// 2s后输出
2
Error: 2
// 4s后输出
4

.all()作用是接收一组异步任务, 当只有全部的异步任务都是成功的状态时它才成功,对应的值为所有异步任务的值组成的数组,当有存在失败的状态时,它的状态为失败,值为第一个状态为失败的异步任务的值。需要注意的是.catch是会捕获最先的那个异常,在这道题目中最先的异常就是runReject(2)的结果。

第二十题

下面代码输出什么?


function runAsync(x) {
  const p = new Promise(r =>
    setTimeout(() => r(x, console.log(x)), 1000)
  );
  return p;
}
function runReject(x) {
  const p = new Promise((res, rej) =>
    setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
  );
  return p;
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log("result: ", res))
  .catch(err => console.log(err));

输出:

0
'Error: 0'
//1s后
1
2
3

.race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。

好啦!本文到这里就结束了,你做对了几道?

你可能感兴趣的:(前端汇总,JavaScript,前端,javascript,promise,异步,面试题,宏任务,微任务,async,await)