写在前面:本帖用于复习异步/事件循环的编程题目,验证阶段性的学习成果,欢迎大家批评指正!
事件循环:js在执行代码的过程中,会优先执行执行栈中的同步代码,当遇到异步代码时,会将异步代码放入任务队列中。当执行栈执行完毕后,再从任务队列中提取代码并执行。任务队列又分为宏任务队列与微任务队列,执行顺序为先微后宏。
知识点:
- 宏任务队列:setTimeOut/setInternal
- 微任务队列:promise中的then/catch;async/await
- 执行顺序为先微后宏
- await会阻塞当前的代码执行,阻塞代码块中剩余未执行的语句,可以看作将后续代码放入微任务队列中。
- promsie值穿透问题,当链式调用的过程中,本层没有返回结果,会默认将上层传递的值向下传递。
- then和catch会捕获异常,当异常被捕获后就不会继续向下传递,当二者同时存在,按照先后顺序竞争。
- finally是then的特例,不接受参数,默认return上层的返回值,遇到异常则向下抛出。
第1题,难度:🌟
考察知识点,简单事件循环
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
/*
1
2
4
*/
解答: 流程一:
1-4:同步代码,打印1 2。
5-7:遇到then,将代码6行放入微任务队列中。
8:执行同步代码8,打印4。
| 微 | 宏 |
|---|---|
| 6 |
流程二:
6:由于代码6行中then方法并没有得到链式调用返回的resolve,故会一直等待状态改变,并不会执行。
最终打印结果1 2 4
第2题,难度:🌟🌟
考察知识点,简单事件循环+promise状态变化
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
流程一:
2:同步代码,打印'promise1'
3:resolve,promise1的状态变为成功
5-7:then异步,放入微任务队列
8:同步,打印 1 Promise{resolve1}
9:同步,打印 2 Promise{ pending },此时promise2的状态还未改变
| 微 | 宏 |
|---|---|
| 5-7 |
流程2:执行微任务
5-7:打印res,也就是promise中传递的值,打印resolve1
第3题,难度:🌟🌟
考察知识点,简单事件循环+promise状态变化
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);
1
2
4
timerStart
timerEnd
success
流程一:
2:同步执行,打印1
3-7:放入宏任务队列
8:同步执行,打印2
10-12:放入微任务队列
13:同步执行,打印4
| 微 | 宏 |
|---|---|
| 10-12 | 3-7 |
流程2:先微后宏
10-12:执行微任务,打印res,res是promise1的结果,由于promise1此时并没有执行到第5行的resolve返回结果,所以等待。
3-7:执行宏任务,打印timerStart timerEnd
流程3:
10-12:promise1的状态发生改变,打印res的结果:success
第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');
start
promise1
timer1
promise2
timer2
流程1:
1-6:then方法,放入微任务队列
7-12:放入宏任务队列
13:同步,打印 start
| 微 | 宏 |
|---|---|
| 1-6 | 7-12 |
流程2:先微后宏
1-6:执行代码2,打印promise1。执行3-5,添加到宏任务队列。此时的宏任务队列为:
| 微 | 宏 |
|---|---|
| 7-12 | |
| 3-5 |
7-12:执行代码8,微任务执行完成,开始执行宏任务,打印timer1。执行9-11,添加到微任务队列。此时的任务队列:
| 微 | 宏 |
|---|---|
| 9-11 | 3-5 |
流程3:执行完一个宏任务后,发现微任务队列不为空,开始重新执行先微后宏。
9-11:微任务,打印 promise2
3-5:宏任务,打印 timer2
第5题,难度:🌟
考察知识点,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
流程1:
2-4:同步代码,分别执行resolve/reject/resolve,但是promise只能改变一次状态,先到先得,代码2有效。
6-9:then方法,放入微任务队列。
| 微 | 宏 |
|---|---|
| 6-9 |
流程2:先微后宏
6-9:打印 then:success1。由于没有错误,所以catch中的语句不执行
第6题,难度:🌟
考察知识点,promise状态变化
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
1
Promise {<fulfilled>: undefined}
当promise没有返回值,则发生值穿透。例如resolve(1)的值在链式调用过程中,没有被其他return覆盖,故发生了值穿透,最后在console.log中进行打印。
Promise.resolve(1)
.then(2)
.then( () => {return Promise.resolve(3)})
.then(console.log)
3
若链式调用发生了覆盖,则结果变为3
第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!!}
流程1:
1-5:放入宏任务
6-8:放入微任务
9-10:同步任务,打印promise1 promise2,但是两个promise的状态均未改变,所以都是pending
11-14:放入宏任务
流程2:先微后宏
| 微 | 宏 |
|---|---|
| 6-8 | 1-5 |
| 11-14 |
6-8:微任务,但是此时promise1的状态还未改变,then不能执行。
1-5:宏任务,改变了promise1的状态
6-8:p1状态终于改变,代码7抛出异常。
11-14:宏任务,打印p1和p2的状态,p1的结果是成功,p2的结果是异常。
第8题,难度:🌟
考察知识点,promise异常捕获
Promise.resolve(1)
.then(res => {
console.log(res);
return 2;
})
.catch(err => {
return 3;
})
.then(res => {
console.log(res);
});
1
2
1:返回成功状态的结果1
2-3:打印1
4:返回2,会被自动包裹在resolve中,表示为成功状态
6-8:代码不执行,因为状态是成功的,不会进入catch
9-10:打印返回的结果2
第9题,难度:🌟🌟
考察知识点,promise异常捕获
Promise.resolve().then(() => {
return new Error('error!!!')
}).then(res => {
console.log("then: ", res)
}).catch(err => {
console.log("catch: ", err)
})
"then: " "Error: error!!!"
return返回的结果会默认包裹一层resolve,所以最终结果并没有被catch捕获,转而被代码4进行打印。
第10题,难度:🌟
考察知识点,promsie死循环
const promise = Promise.resolve().then(() => {
return promise;
})
promise.catch(console.err)
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
返回值是自身,发生了死循环
第11题,难度:🌟
考察知识点,promsie值透传
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
1
链式调用中没有return/resolve/reject返回值,发生透传,将1一直传递下去
第12题,难度:🌟
考察知识点,promise异常捕获
Promise.reject('err!!!')
.then((res) => {
console.log('success', res)
}, (err) => {
console.log('error', err)
}).catch(err => {
console.log('catch', err)
})
error err!!!
then和catch会按照先后顺序进行捕获,捕获后就不会向下传递。then在前,打印error,不会再次进入catch。
第13题,难度:🌟
考察知识点,promsie状态变化
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
如果两个promise中都有then方法,会逐个进入微任务队列,交替执行。
p1的then推入微任务 p2的finally推入微任务
执行p1.then,打印1 p1的finally推入微任务
执行p2.finally,打印finally2 p2的then推入微任务
执行p1.finally,打印finally 执行p2.then,打印finally2后面的then函数 2。finally 默认return的是上层的结果,异常除外。
第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))
1
2
3
[1, 2, 3]
promise.all的考察,在一秒后同时打印1,2,3,以及所有成功的列表
第15题,难度:🌟🌟
考察知识点,promsie状态变化
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))
1
3
2
Error: 2
4
在all方法中依次执行方法runAsync(1), runReject(4), runAsync(3), runReject(2)分别简称为a1,r4,a3,r2,由于async方法是异步代码,会推入宏任务队列,r2方法会延迟2s,r4方法会延迟4s。
先执行a1,延迟1秒
执行a3,延迟1秒
执行r2,延迟2秒
执行r4,延迟4秒
故执行顺序为a1,a3,r2,r4,当代码执行到r2时,会使得all方法结束,返回失败的状态。
故打印catch方法中打印r2
第16题,难度:🌟
考察知识点,promsie状态变化
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
race会返回最先执行的结果,由于三个异步方法都是延迟一秒执行,则r1最先执行,并改变race的状态。
故打印顺序为 1 'result: ' 1 2 3
第17题,难度:🌟
考察知识点,promsie状态变化
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
r0延迟0s,a1延迟1s,a2延迟1s,a3延迟1s
故race状态被最快执行的r0改变,故先打印 0 error:0
随后打印 2 3
第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
流程1:
9:执行代码2,打印 "async1 start"
3:遇到await语句,执行代码7,打印 "async2" ,阻塞代码,将后续代码放入到微任务队列
10:执行同步代码,打印"start"
| 微 | 宏 |
|---|---|
| 3-4 | |
流程2:先微后宏
执行4:打印 "async1 end"
第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
流程1:
15:同步执行async1方法
2:执行代码2,打印 "async1 start",await阻塞后续代码,放入微任务队列
3:执行代码3,推入宏任务队列,执行同步代码,打印"async2"
16:推入宏任务队列
19:执行同步代码,打印"start"
| 微 | 宏 |
|---|---|
| 4-7 | 10-12 |
| 16-18 |
流程2:先微后宏
4-7:执行代码4,打印"async1 end",将后续代码推入到宏任务队列中。
微任务执行完毕,此时的宏任务队列
| 微 | 宏 |
|---|---|
| 10-12 | |
| 16-18 | |
| 5-7 |
10-12:打印"timer2" 16-18:打印"timer3" 5-7:打印"timer1"
第20题,难度:🌟🌟
考察知识点,promise状态变化
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')
srcipt start
async1 start
promise1
srcipt end
流程1:
9:同步代码,打印'srcipt start'
10:执行代码2,打印'async1 start',await异步代码,打印'promise1',并将后续代码推入到微任务队列
11:打印 'srcipt end'
| 微 | 宏 |
|---|---|
| 6-7 | |
| 10 |
流程2:先微后宏
6-7:由于promise状态未改变,所以不会打印'async1 success' 10:状态未改变,不会打印'async1 end'
第21题,难度:🌟
考察知识点,promise状态改变
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')
srcipt start
async1 start
promise1
srcipt end
promise1 resolve
async1 success
async1 end
流程1:
10:同步执行,打印 srcipt start
11:同步执行2,打印async1 start,同步执行4,打印promise1,将后续代码推入到微任务队列。
| 微 | 宏 |
|---|---|
| 6 | |
| 7-8 | |
| 11 |
12:同步执行,打印srcipt end
流程2:先微后宏
6:打印promise1 resolve
7-8:打印async1 success
11:promise状态改变,打印'async1 end'
第22题,难度:🌟
考察知识点,promise状态改变
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(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
流程1:
11:同步代码,打印"script start"
13-15:推入宏任务队列
17:执行同步代码2,打印async1 start,执行代码3,打印async2,将4推入微任务队列。
19:同步执行20,打印promise1
20:将22-24,推入微任务队列
25:同步执行,打印script end
| 微 | 宏 |
|---|---|
| 4 | 13-15 |
| 22-24 | |
流程2:先微后宏
4:打印async1 end
22-24:打印promise2
13-15:宏任务,打印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
流程1:
12:执行async1,执行代码2,运行async2方法,将2-4推入到微任务队列。执行代码8,打印async2.抛出的error导致程序结束。
第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}
流程1:
17:同步执行代码2,打印3,同步执行代码4,打印7,将5-9推入宏任务队列。p的状态发生改变。将13-15推入微任务队列。执行完毕,将18推入微任务队列。
20:同步执行,打印4
| 微 | 宏 |
|---|---|
| 13-15 | 5-9 |
| 18 | |
流程2:
13-15:resolve(1)先改变了p的状态,故打印1
18:resolve(2)改变了first的结果,故打印2
5-9:宏任务,执行代码6,打印5,执行代码8,打印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
流程1:
12:同步执行,打印script start
13:执行a1
2:打印async1,将4推入宏任务队列
6-8:打印promise1,将awiat后的9-10推入微任务队列
13:a1暂时执行完毕,将then推入微任务
14:同步,打印
16:then放入微任务
20-22:推入宏任务
| 微 | 宏 |
|---|---|
| 9-10 | 4 |
| 13 | 20-22 |
| 16 |
流程2:先微后宏
9-10:由于await代码块并没有返回结果,导致后续代码不会执行,不会打印async1 end
13:a1的状态未改变,不打印async1 success
16:发生值传透,执行代码19,打印1
4:执行宏任务,等待2s后打印timer1
20-22:等待1s后打印timer2
第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}
流程1:
6:将2-5放入宏任务,将p1的状态改变
| 微 | 宏 |
|---|---|
| 8-13 | 2-5 |
流程2:先微后宏
9:打印resolve1
10-12:放入宏任务,将finally放入微任务
| 微 | 宏 |
|---|---|
| 14 | 2-5 |
| 10-12 |
14:继续执行微任务,finally默认是上层的返回结果,但上层的then方法并没有返回值,打印finally undefined
2-5:打印timer1
10-12:此时的p1的值被then方法改变,没有返回值,打印成功状态下的undefined
第27题,难度:🌟🌟
考察知识点,复杂事件循环
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
1
7
6
8
2
4
3
5
9
11
10
12
流程1:
1:打印1
3-14:推入宏任务
15-17:微任务
18:打印7
19:改变p的状态
21-23:微任务
25-36:宏任务
| 微 | 宏 |
|---|---|
| 15-17 | 3-14 |
| 21-23 | 25-36 |
流程2:
15-17:打印6
21-23:由于p的状态改变,打印8
3-14:开始宏任务
4:打印2
5-7:放入微任务
9:打印4,改变了内部p的状态
12:放入微任务
| 微 | 宏 |
|---|---|
| 5-7 | 25-36 |
| 12 |
此时微任务队列不为空,清空微任务
5-7:打印3
12:状态改变,then方法执行,打印5
25-36:开始宏任务
26:打印9
27-29:放入微任务
31:打印11,新p的状态改变
34:放入微任务
| 微 | 宏 |
|---|---|
| 27-29 | |
| 31 |
27-29:打印10
31:打印12
第28题,难度:🌟🌟
考察知识点,复杂事件循环
console.log(1)
setTimeout(() => {
console.log(2)
})
new Promise(resolve => {
console.log(3)
resolve(4)
}).then(d => console.log(d))
setTimeout(() => {
console.log(5)
new Promise(resolve => {
resolve(6)
}).then(d => console.log(d))
})
setTimeout(() => {
console.log(7)
})
console.log(8)
1
3
8
4
2
5
6
7
流程1:
1:打印1
3-5:宏任务
8:打印3,改变p的状态
10:then放入微任务
12-17:宏任务
19-21:宏任务
23:打印8
| 微 | 宏 |
|---|---|
| 10 | 3-5 |
| 12-17 | |
| 19-21 |
流程2:先微后宏
10:then状态改变,打印4
3-5:打印2
12-17:打印5
15:新p的状态改变
16:放入微任务
| 微 | 宏 |
|---|---|
| 16 | 19-21 |
16:打印6
19-21:打印7
第29题,难度:🌟
考察知识点,简单事件循环
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
})
setTimeout(() => {
console.log(6);
})
console.log(7);
1
4
7
5
2
3
6
1:同步执行,打印1
3-8:宏任务
11:打印4
13-15:微任务
17-19:宏任务
21:打印7
| 微 | 宏 |
|---|---|
| 13-15 | 2-8 |
| 17-19 |
13-15:then状态改变,打印5
2-8:打印2,将6放入微任务
| 微 | 宏 |
|---|---|
| 6 | 17-19 |
6:打印3
17-19:打印6
第30题,难度:🌟
考察知识点,promsie状态变化
Promise.resolve().then(() => {
console.log('1');
throw 'Error';
}).then(() => {
console.log('2');
}).catch(() => {
console.log('3');
throw 'Error';
}).then(() => {
console.log('4');
}).catch(() => {
console.log('5');
}).then(() => {
console.log('6');
});
1
3
5
6
第31题,难度:🌟
考察知识点,简单事件循环
setTimeout(function () {
console.log(1);
}, 100);
new Promise(function (resolve) {
console.log(2);
resolve();
console.log(3);
}).then(function () {
console.log(4);
new Promise((resove, reject) => {
console.log(5);
setTimeout(() => {
console.log(6);
}, 10);
})
});
console.log(7);
console.log(8);
2
3
7
8
4
5
6
1
9.16更新至31题-----------------------------------------------------------------------------------------------
参考文献:
- 前端面试题汇总(www.yuque.com/cuggz/inter…
- 前端面试宝典(fe.ecool.fun/)