Promise面试题

目录

准备

第一题

第二题

第三题

第四题

第五题

第六题

第七题

第八题

第九题

第十题

第十一题

第十二题

第十三题

第十四题

第十五题

第十六题

第十七题

第十八题

第十九题

第二十题


准备

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

第一题

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

javascript 复制代码
setTimeout(() => {
  console.log('timer1');
  setTimeout(() => {
    console.log('timer3')
  }, 0)
}, 0)
setTimeout(() => {
  console.log('timer2')
}, 0)
console.log('start')
javascript 复制代码
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。

第二题

下面代码输出什么?

javascript 复制代码
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。

第三题

下面代码输出什么?

javascript 复制代码
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。

第四题

下面代码输出什么?

javascript 复制代码
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。

第五题

下面代码输出什么?

javascript 复制代码
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的回调。

第六题

下面代码输出什么?

javascript 复制代码
const promise = Promise.resolve().then(() => {
  return promise;
})
promise.catch(console.err)

输出:

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

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

第七题

下面代码输出什么?

javascript 复制代码
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

输出:

1

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

第八题

下面代码输出什么?

javascript 复制代码
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函数捕获。

第九题

下面代码输出什么?

javascript 复制代码
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。

第十题

下面代码输出什么?

javascript 复制代码
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。

第十一题

下面代码输出什么?

javascript 复制代码
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的回调函数进行输出。

第十二题

下面代码输出什么?

javascript 复制代码
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,后续的代码需要等到同步执行完毕之后才能够执行。套路是跟上面一样的。知识代码稍微绕了一些。

第十三题

下面代码输出什么?

javascript 复制代码
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来进行捕获错误,则会继续执行下去,对应的代码如下:

javascript 复制代码
  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))

第十四题

下面代码输出什么?

javascript 复制代码
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{<resolved>: 1}

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

第十五题

下面代码输出什么?

javascript 复制代码
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回调前面也有讲过会发送值穿透的问题。若这两点能够理解这道题的答案也就出来了。

第十六题

下面代码输出什么?

javascript 复制代码
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{<resolved>: undefined}

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

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

第十七题

下面代码输出什么?

javascript 复制代码
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'。

第十八题

下面代码输出什么?

javascript 复制代码
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()。其他的没啥问题。

第十九题

下面代码输出什么?

javascript 复制代码
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)的结果。

第二十题

下面代码输出什么?

javascript 复制代码
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()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。

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

相关推荐
东风破_19 小时前
LLM 为什么记不住你?无状态、messages 和上下文管理
javascript·人工智能
梨子同志19 小时前
qiankun
前端
梨子同志19 小时前
Nginx
前端
梨子同志19 小时前
NestJS
前端
梨子同志19 小时前
Next.js
前端
谭光志19 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
Hilaku19 小时前
前端架构师的克制:什么时候该坚决对新技术说不?
前端·javascript·程序员
陈随易20 小时前
前端项目部署只要30秒
前端·后端·程序员
本末倒置18320 小时前
从 Vue CLI 迁移到 Rsbuild 后,JSX 突然全挂了?一次排查记录
前端
古夕20 小时前
多应用同域部署下 Favicon 异常问题排查与治理
前端·javascript