大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺
关注微信公众号:前端充电宝,获取最新原创文章!
目录
下面题目主要和Promise以及JS的事件循环有关,题目均为代码输出问题(面试题从文章第二部分开始)。
Promise回顾
首先来回顾一下Promise是什么。
Promise 是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理和更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
(1)Promise的实例有三个状态:
- Pending(进行中)
- Resolved(已完成)
- Rejected(已拒绝)
当把一件事情交给promise时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected。
(2)Promise的实例有两个过程:
- pending -> fulfilled : Resolved(已完成)
- pending -> rejected:Rejected(已拒绝)
需要注意:一旦从进行状态变成为其他状态就永远不能更改状态了。
特点:
- 将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。流程更加清晰,代码更加优雅。
- Promise对象提供统一的接口,使得控制异步操作更加容易。
缺点:
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
实例:
当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的:
new Promise((resolve, reject) => {
console.log('new Promise')
resolve('success')
})
console.log('finifsh')
// new Promise -> finifsh
Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装
Promise.resolve(1)
.then(res => {
console.log(res) // => 1
return 2 // 包装成 Promise.resolve(2)
})
.then(res => {
console.log(res) // => 2
})
当然了,Promise 也很好地解决了回调地狱的问题,可以把之前的回调地狱例子改写为如下代码:
ajax(url)
.then(res => {
console.log(res)
return ajax(url1)
}).then(res => {
console.log(res)
return ajax(url2)
}).then(res => console.log(res))
前面都是在讲述 Promise 的一些优点和特点,其实它也是存在一些缺点的,比如无法取消 Promise,错误需要通过回调函数捕获。
Promise基础
1、下面代码的执行结果是
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
最后应该输出1 2 4。这样最主要的就是3,要知道promise.then是微任务,会在所有的宏任务执行完之后才会执行,同时需要promise内部的状态发生变化,因为这里内部没有发生变化,所以不输出3。
2、下面代码的执行结果是
const promise1 = new Promise((resolve, reject) => {
console.log('promise1')
resolve('resolve1')
})
const promise2 = promise1.then(res => {
console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
输出结果如下:
'promise1'
'1' Promise{
<resolved>: 'resolve1'}
'2' Promise{
<pending>}
'resolve1'
需要注意的是,直接打印promise1,会打印出它的状态值和参数。
这里说一下这道题的具体思路:
- script是一个宏任务,按照顺序执行这些代码
- 首先进入Promise,执行该构造函数中的代码,打印
promise1
- 碰到
resolve
函数, 将promise1
的状态改变为resolved
, 并将结果保存下来 - 碰到
promise1.then
这个微任务,将它放入微任务队列 promise2
是一个新的状态为pending
的Promise
- 执行同步代码1, 同时打印出
promise1
的状态是resolved
- 执行同步代码2,同时打印出
promise2
的状态是pending
- 宏任务执行完毕,查找微任务队列,发现
promise1.then
这个微任务且状态为resolved
,执行它。
这样,就执行完了所有的的代码。
3、下面代码的执行结果是
const promise = new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {
console.log("timerStart");
resolve("success");
console.log("timerEnd");
}, 0);
console.log(2);
});
promise.then((res) => {
console.log(res);
});
console.log(4);
- 首先遇到Promise构造函数,会先执行里面的内容,打印1
- 遇到
steTimeout
,它是一个宏任务,被推入宏任务队列 - 接下继续执行,打印出2
- 由于
Promise
的状态此时还是pending
,所以promise.then
先不执行 - 继续执行下面的同步任务,打印出4
- 微任务队列此时没有任务,继续执行下一轮宏任务,执行
steTimeout
- 首先执行
timerStart
,然后遇到了resolve
,将promise
的状态改为resolved
且保存结果并将之前的promise.then
推入微任务队列,再执行timerEnd
- 执行完这个宏任务,就去执行微任务
promise.then
,打印出resolve
的结果
4、 下面代码的执行结果是
Promise.resolve().then(() => {
console.log('promise1');
const timer2 = setTimeout(() => {
console.log('timer2')
}, 0)
});
const timer1 = setTimeout(() => {
console.log('timer1')
Promise.resolve().then(() => {
console.log('promise2')
})
}, 0)
console.log('start');
这个题目就有点绕了,下面来梳理一下:
- 首先,
Promise.resolve().then
是一个微任务,加入微任务队列 - 执行timer1,它是一个宏任务,加入宏任务队列
- 继续执行下面的同步代码,打印出start
- 这样第一轮的宏任务就执行完了,开始执行微任务,打印出
promise1
- 遇到timer2,它是一个宏任务,将其加入宏任务队列
- 这样第一轮的微任务就执行完了,开始执行第二轮宏任务,指执行定时器timer1,打印timer1
- 遇到Promise,它是一个微任务,加入微任务队列
- 开始执行微任务队列中的任务,打印promise2
- 最后执行宏任务timer2定时器,打印出timer2
这个题目还是比较复杂的,值得去认真理解一下。
执行结果:
'start'
'promise1'
'timer1'
'promise2'
'timer2'
5、 下面代码的执行结果是
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
的状态在发生变化之后,就不会再发生变化。开始状态由pending
变为resolve
,说明已经变为已完成状态,下面的两个状态的就不会再执行,同时下面的catch也不会捕获到错误。
6、下面代码的执行结果是
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
执行结果为:
1
Promise {
<fulfilled>: undefined}
Promise.resolve方法的参数如果是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的Promise对象,状态为resolved,Promise.resolve方法的参数,会同时传给回调函数。
then方法接受的参数是函数,而如果传递的并非是一个函数,它实际上会将其解释为then(null),这就会导致前一个Promise的结果会传递下面。
7、下面代码的执行结果是
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 {
<pending>}
promise2 Promise {
<pending>}
Uncaught (in promise) Error: error!!!
promise1 Promise {
<fulfilled>: "success"}
promise2 Promise {
<rejected>: Error: error!!}
这个就比较好理解了,和上面的几个题目思路类似。
Promise的catch、then、finally
8、下面代码的执行结果是
Promise.resolve(1)
.then(res => {
console.log(res);
return 2;
})
.catch(err => {
return 3;
})
.then(res => {
console.log(res);
});
输出结果为:1 2
Promise可以链式调用,因为每次调用 .then
或者 .catch
都会返回一个新的 promise,从而实现了链式调用, 它并不像一般我们任务的链式调用一样return this。
上面的输出结果之所以依次打印出1和2,是因为resolve(1)
之后走的是第一个then方法,并没有走catch里,所以第二个then中的res得到的实际上是第一个then的返回值。并且return 2会被包装成resolve(2)
,被最后的then打印输出2。
9、下面代码的执行结果是
Promise.resolve().then(() => {
return new Error('error!!!')
}).then(res => {
console.log("then: ", res)
}).catch(err => {
console.log("catch: ", err)
})
返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!')
也被包裹成了return Promise.resolve(new Error('error!!!'))
。
因此它被then捕获而不是catch,输出结果为:
"then: " "Error: error!!!"
10、下面代码的执行结果是
const promise = Promise.resolve().then(() => {
return promise;
})
promise.catch(console.err)
这里其实是一个坑,.then
或 .catch
返回的值不能是 promise 本身,否则会造成死循环。所以这道题会报错:
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
11、下面代码的执行结果是
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
看到这个题目,好多的then,实际上我们只需要记住一个原则:.then
或.catch
的参数期望是函数,传入非函数则会发生值透传。
第一个then和第二个then中传入的都不是函数,一个是数字,一个是对象,因此发生了透传,将resolve(1)
的值直接传到最后一个then里。
所以输出结果为:1
12、下面代码的执行结果是
Promise.reject('err!!!')
.then((res) => {
console.log('success', res)
}, (err) => {
console.log('error', err)
}).catch(err => {
console.log('catch', err)
})
.then
函数中的两个参数:
- 第一个参数是用来处理Promise成功的函数
- 第二个则是处理失败的函数
也就是说Promise.resolve('1')
的值会进入成功的函数,Promise.reject('2')
的值会进入失败的函数。
在这道题中,错误直接被then
的第二个参数捕获了,所以就不会被catch
捕获了,输出结果为:'error' 'error!!!'
但是,如果是像下面这样:
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)
})
在then
的第一参数中抛出了错误,那么他就不会被第二个参数不活了,而是被后面的catch
捕获到,所以输出结果为:
fail2 Error: error!!!
at success
13、下面代码的执行结果是
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)
})
.finally()
一般用的很少,只要记住以下几点就可以了:
.finally()
方法不管Promise对象最后的状态如何都会执行.finally()
方法的回调函数不接受任何的参数,也就是说你在.finally()
函数中是无法知道Promise最终的状态是resolved
还是rejected
的- 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。
- finally本质上是then方法的特例
上面的代码的输出结果为:
1
finally2
finally
finally2后面的then函数 2
再开看一下.finally()
的错误捕获:
Promise.resolve('1')
.finally(() => {
console.log('finally1')
throw new Error('我是finally中抛出的异常')
})
.then(res => {
console.log('finally后面的then函数', res)
})
.catch(err => {
console.log('捕获错误', err)
})
输出结果为:
'finally1'
'捕获错误' Error: 我是finally中抛出的异常
Promise的all和race
-
.all()
的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。 -
.race()
的作用是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
14、下面代码的执行结果是
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))
首先,我们定义了一个Promise,来异步执行函数runAsync,该函数传入一个值x,然后间隔一秒后打印出这个x。
之后再使用Promise.all
来执行这个函数,结果如下:
1
2
3
[1, 2, 3]
执行的时候,看到一秒之后输出了1,2,3,同时输出了数组[1, 2, 3],三个函数是同步执行的,并且在一个回调函数中返回了所有的结果。并且结果和函数的执行顺序是一致的。
15、下面代码的执行结果是
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
我们可以看到。catch捕获到了第一个错误,在这道题目中最先的错误就是runReject(2)
的结果。
如果一组异步操作中有一个异常都不会进入.then()
的第一个回调函数参数中。会被.then()
的第二个回调函数捕获。
16、下面代码的执行结果是
下面再来看一下race:
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log('result: ', res))
.catch(err => console.log(err))
执行结果:
1
'result: ' 1
2
3
then只会捕获第一个成功的方法,其他的函数虽然还会继续执行,但是不是被then捕获了。
17、下面代码的执行结果是
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
1
2
3
可以看到在catch捕获到第一个错误之后,后面的代码还不执行,不过不会再被捕获了。
注意:all
和race
传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行。
Async、await
说到Promise就不得不提以下async和await,他们同样是用来执行异步代码。
18、下面代码的执行结果是
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
- 首先执行函数中的同步代码
async1 start
,之后碰到了await
,它会阻塞async1
后面代码的执行,因此会先去执行async2
中的同步代码async2
,然后跳出async1
- 跳出
async1
函数后,执行同步代码start
- 在一轮宏任务全部执行完之后,再来执行
await
后面的内容async1 end
这里可以理解为await后面的语句相当于放到了new Promise中,下一行及之后的语句相当于放在Promise.then中。
19、下面代码的执行结果是
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
setTimeout(() => {
console.log('timer1')
}, 0)
}
async function async2() {
setTimeout(() => {
console.log('timer2')
}, 0)
console.log("async2");
}
async1();
setTimeout(() => {
console.log('timer3')
}, 0)
console.log("start")
输出结果为:
async1 start
async2
start
async1 end
timer2
timer3
timer1
这个题目就稍微就有点麻烦了。看一下执行的步骤:
- 首先进入
async1
,打印出async1 start
,这个是毋庸置疑的 - 之后遇到
async2
,进入async2
,遇到定时器timer2
,加入宏任务队列,之后打印async2
- 由于
async2
阻塞了后面代码的执行,所以执行后面的定时器timer3
,将其加入宏任务队列,之后打印start
- 然后执行async2后面的代码,打印出
async1 end
,遇到定时器timer1,将其加入宏任务队列 - 最后,宏任务队列有三个任务,先后顺序为
timer2
,timer3
,timer1
,没有微任务,所以直接所有的宏任务按照先进先出的原则执行。
实际上也不是很难,只要理清事件循环机制,就很容易做出来啦!
20、下面代码的执行结果是
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
之后的内容是不会执行的,也包括async1
后面的 .then
。
21、下面代码的执行结果是
async function async1 () {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
resolve('promise1 resolve')
}).then(res => console.log(res))
console.log('async1 success');
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
这里对上面一题进行了改造,加上了resolve,来看看输出结果:
script start
async1 start
promise1
script end
promise1 resolve
async1 success
async1 end
这个就不难理解了,不多解释。
22、下面代码的执行结果是
来看一道字节跳动面试题:
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
也不多解释了,只是流程比较长,实际上难度并不是很大。
23、下面代码的执行结果是
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
我们可以看到,如果async函数中抛出了错误,就会终止错误结果,不会继续向下执行。
如果想要让错误不足之处后面的代码执行,可以使用catch来捕获:
async function async1 () {
await Promise.reject('error!!!').catch(e => console.log(e))
console.log('async1');
return Promise.resolve('async1 success')
}
async1().then(res => console.log(res))
console.log('script start')
这样的输出结果就是:
script start
error!!!
async1
async1 success
其他题目
24、下面代码的执行结果是
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}
说一下执行的步骤吧:
- 首先会进入Promise,打印出3,之后进入下面的Promise,打印出7
- 遇到了定时器,将其加入宏任务队列
- 执行Promise p中的resolve,状态变为resolved,返回值为1
- 执行Promise first中的resolve,状态变为resolved,返回值为2
- 遇到p.then,将其加入微任务队列,遇到first().then,将其加入任务队列
- 执行外面的代码,打印出4
- 这样第一轮宏任务就执行完了,开始执行微任务队列中的任务,先后打印出1和2
- 这样微任务就执行完了,开始执行下一轮宏任务,宏任务队列中有一个定时器,执行它,打印出5,由于执行已经变为resolved状态,所以
resolve(6)
不会再执行 - 最后
console.log(p)
打印出Promise{<resolved>: 1}
25、下面代码的执行结果是
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
这道题比较简单,简单说一下执行的过程:
- 首先执行同步带吗,打印出script start
- 遇到定时器timer1将其加入宏任务队列
- 之后是执行Promise,打印出promise1,由于Promise没有返回值,所以后面的代码不会执行
- 然后执行同步代码,打印出script end
- 继续执行下面的Promise,.then和.catch期望参数是一个函数,这里传入的是一个数字,因此就会发生值渗透,将resolve(1)的值传到最后一个then,直接打印出1
- 遇到第二个定时器,将其加入到微任务队列,执行微任务队列,按顺序依次执行两个定时器,但是由于定时器时间的原因,会在两秒后先打印出timer2,在四秒后打印出timer1
26、下面代码的执行结果是
const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve('resolve3');
console.log('timer1')
}, 0)
resolve('resovle1');
resolve('resolve2');
}).then(res => {
console.log(res) // resolve1
setTimeout(() => {
console.log(p1)
}, 1000)
}).finally(res => {
console.log('finally', res)
})
执行结果为:
resolve1
finally undefined
timer1
Promise{
<resolved>: undefined}
需要注意的是最后一个定时器打印出的p1其实是.finally
的返回值,我们知道.finally
的返回值如果在没有抛出错误的情况下默认会是上一个Promise的返回值,而这道题中.finally
上一个Promise是.then()
,但是这个.then()
并没有返回值,所以p1打印出来的Promise的值会是undefined
,如果在定时器的下面加上一个return 1
,则值就会变成1。
最后
关于Promise代码输出的问题就先说这些吧,实际上,代码输出问题往往和事件循环结合在一起,只要理解了事件循环的过程,做这些题目也就毫无压力了。其实,Promise的面试题还是有很多的,不止有代码输出题,还有手写代码,Promise原理,实现Promise A+等等,后面有时间再总结!
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/175104.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...