笔者在把手写Promise源码拿下以后,下面的这些题才融会贯通.如果发现实在理解不了,可以先去学习一下手写Promise系列.然后在做题.
1.考察点:只有调用resolve
或者reject
才会改变状态,触发回调
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
只有在调用了resolve
,或者reject
这两个回调,才可以改变状态,触发微任务的的回调。
2.考察点:Promise
在then
后面的状态,是由其中的回调函数决定的。
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
描述一下流程:
1.script是一个宏任务,按照顺序执行这些代码
2.首先进入Promise
执行构造函数,打印promise1
3.同步执行resolve("resolve")
,改变Promise
状态为Resolved
。同时触发回调。
4.碰到promise1.then
这个微任务,将它放入微任务队列。
5.执行同步代码1,输出promise1
的状态,它的状态已经确定为Resolved
6.执行同步代码2,输出promise2
的状态,它的状态因为需要微任务队列中的回调执行完成之后才能知道
所以打印出来的是pending
状态的promise
.
7.宏任务执行完毕,开始执行微任务,这个时候开始执行console.log(res)
.而res
的值是resolve1
.
8.如果我们延迟打印Promise2
的状态,那么它最后也会变成Resolved
,如果回调中返回的是非Promise
的数据,包括Undefined
。那么
状态就是resolved
这道题考察的是:Promise
在then
后面的状态,是由其中的回调函数决定的。
1.如果是非Promise
.则状态为resolved
,
2.如果是Promise
,则取决于该Promise
的状态
3.如果没有执行,则状态是pending
,需要等到微任务队列回调完成。
4.如果里面报错 ,或者抛出错误,状态会变为rejected
3.考察点:resolve
执行
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
代码执行过程如下:
1.执行Promise
的构造函数,它是同步执行的。所以打印 1
2.碰到setTimeout
,假如宏任务队列。
3.打印出2
4.碰到 promise.then
,但是它需要等到微任务队列的执行回调。
5.打印4
6.宏任务执行完毕,没有微任务,执行setTimeout
.打印timerStart
7.执行resolve
回调,改变Promise
的内部状态为resolved
,同时把回调任务压入微任务队列。
8.继续执行打印timerEnd
9.宏任务执行完毕,检查微任务对比。打印console.log(res)
.
这道题考察的是Promise
的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');
输出结果
start
promise1
timer1
promise2
timer2
代码执行过程如下:
1.首先Promise.resolve().then
会在微任务队列中添加一个任务.
2.执行time1
.给宏任务队列添加一个任务
3.打印start
4.本轮宏任务执行完毕,开始检查微任务队列,执行打印promise1
5.执行time2
,在宏任务队列中添加任务.
6.微任务执行完毕.开始执行宏任务.会先执行先添加到宏任务队列的代码.打印time1
.
7.执行Promise.resolve().then
,在本轮宏任务中的微任务队列添加任务.
8.宏任务执行完毕,检查执行微任务队列.打印promsie2
.
9.微任务执行完毕,执行宏任务,打印time2
.
这道题考察的是Promise.resolve()
的执行会返回一个resolve
状态的Promise
.
同时会调用它的then
方法中的回调函数,把一个微任务添加到微任务队列中去.
另外,宏任务执行完毕之后,会先执行微任务队列.
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
本题考察的点是:Promise
的状态只可以改变一次,所以,后面的代码都不会在执行.
6.考察点:值穿透
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
输出结果:
1
Promise {<fulfilled>: undefined}
考察点:值穿透
这道题的意思是,如果then
方法的参数不是函数,那么它是失效状态.
Promise
的值会一直往下传,直到传到一个函数里面.
7.考察点:Promise
的状态
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的状态
8.考察点:Promise
的catch
和它的位置没有关系
Promise.resolve(1)
.then(res => {
console.log(res);
return 2;
})
.catch(err => {
return 3;
})
.then(res => {
console.log(res);
});
1
2
Promise
是可以链式调用的.同时catch
的回调和它的位置没有关系,只有它的状态变为rejected
才会调用.
9.考察点:then
的结果由其返回值决定.
Promise.resolve().then(() => {
return new Error('error!!!')
}).then(res => {
console.log("then: ", res)
}).catch(err => {
console.log("catch: ", err)
})
//"then: " "Error: error!!!"
返回任意一个非 promise
的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!')
也被包裹成了return
Promise.resolve(new Error('error!!!'))
,因此它会被then捕获而不是catch。
10.考察点:Promise
不能返回它自己
const promise = Promise.resolve().then(() => {
return promise;
})
promise.catch(console.err)
//Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
这里其实是一个坑,.then
或者.catch
返回的值不能是promise
本身.否则会报错.
11.考察点:值穿透
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
//1
看到这个题目,好多的then,实际上只需要记住一个原则:.then 或.catch 的参数期望是函数,传入非函数则会发生值透传。
第一个then
和第二个then
中传入的都不是函数,一个是数字,一个是对象,因此发生了透传,将resolve(1)
的值直接传到最后一个then里,直接打印出1。
12.考察点:then
方法的第二个回调参数
Promise.reject('err!!!')
.then((res) => {
console.log('success', res)
}, (err) => {
console.log('error', err)
}).catch(err => {
console.log('catch', err)
})
// error err!!!
考察点:
.then
方法其实是有两个两个回调函数,第二个回调函数处理失败的情况.
如果这个函数又,则就不会再调用catch
方法.
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
捕获
13.考察点 :finally
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后面的then函数 2
finally
finally2
.finally
一般用的很少,只需要记住一下几点就可以了:
-
.finally
方法不管Promise
对象的状态如何都会执行. -
.finally
方法的回调函数不接受任何参数,也就是说你在.finally()函数中是无法知道Promise最终的状态是resolved还是rejected的. - 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。
- finally本质上是then方法的特例.所以它也会被按照
then
方法那样,按个去插入微任务队列中执行.而不是像catch
那样.
.finally
的错误捕获:
Promise.resolve('1')
.finally(() => {
console.log('finally1')
throw new Error('我是finally中抛出的异常')
})
.then(res => {
console.log('finally后面的then函数', res)
})
.catch(err => {
console.log('捕获错误', err)
})
14.考察点:Promise.all的特性
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
,来异步执行函数runAsync
,该函数传入一个值x,然后间隔一秒后打印出这个x。
之后再使用Promise.all
来执行这个函数,执行的时候,看到一秒之后输出了1,2,3,同时输出了数组[1, 2, 3],三个函数是同步执行的,并且在一个回调函数中返回了所有的结果。并且结果和函数的执行顺序是一致的。
15.考察点:Promise.all
的错误处理
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:考察点:Promise.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.考察点:Promise.race
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
由于竞争关系,第一个执行完的决定Promise.race
的状态.
所以它会回调catch
.
其他的任务虽然会继续执行,但是由于Promise
的状态只能改变一次的特性.
已经不会被处理
18. 考察点:async/await
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.首先执行函数中的同步代码async1 start
,之后遇到了await
,它会阻塞async1
后面代码的执行,因此会先去执行async2
中的同步代码async2
,然后跳出async1
;
2.跳出async1
函数后,执行同步代码start
;
3.在一轮宏任务全部执行完之后,再来执行await
后面的内容async1 end。
这里可以理解为,await
后面的语句想防御放入了new Promise
中.如果它里面有异步代码,那么就会异步执行,如果是同步,当时就是同步执行了.
而下一行及之后的代码,就相当于放入了Promise.then
中了.
19.考察点:async/await
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"
- 碰到
await async2();
执行里面的代码,碰到setTimeout
,在宏任务队列中添加任务 - 打印
async2
-
async2
执行完毕,返回值为undefined
,所以它后面的代码挂起加入微任务队列 - 添加宏任务
time3
- 打印
start
- 本轮宏任务执行完毕,开始执行微任务队列.打印
async1 end
, - 添加宏任务
time1
- 微任务队列执行完毕,开始执行宏任务,打印之前加入的顺序.
time2
,time3
,time1
20.考察点:await
的状态由其后面跟着的东西决定.
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
async1 success
async1 end
我这里做错了.这里需要注意 await
如果后面跟着的是一个Promise
,
那么它的状态由这个Promise
的状态决定.里面的代码可以看到,那个Promise
并没有改变状态,调用resolve
.所以它的状态一直都是pending
.
所以它后面的代码都不会执行,包括了then
方法里面的代码.
正确的结果应该是:
script start
async1 start
promise1
script end
21:考察点:await
的状态由其后面跟着的东西决定.
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
和上面的题类似,区别只是 添加了resolve
来改变状态.
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(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
两个函数,但是并未执行,执行script
中的代码,所以打印出script start
; - 遇到定时器
Settimeout
,它是一个宏任务,将其加入到宏任务队列; - 之后执行函数
async1
,首先打印出async1 start
; - 遇到await,执行
async2
,打印出async2
,并阻断后面代码的执行,将后面的代码加入到微任务队列; - 然后跳出
async1
和async2
,遇到Promise
,打印出promise1
; - 遇到
resolve
,将其加入到微任务队列,然后执行后面的script
代码,打印出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
代码一旦报错,后面的代码都不在执行.
所以如果想要不影响后面代码的执行.可以在报错出添加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.考察点:resolve
执行后下面的代码也会继续执行.
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}
;
关于为什么 1 比 2 先执行 ?
1.resovle(1)执行修改p的状态为resolved
,触发 p的微任务回调.但是这个时候,p的回调队列还是空的.
2.执行resolve,修改first的状态为resolved
.触发resolve的的微任务回调队列.这个时候它也是空的.所以它这里就没有添加到上微任务.因为它的代码还没有执行完毕,它的同步代码执行完毕之后,属于它的first().then()才能添加进去.
3.执行p.then.为p添加微任务回调,发现它的内部已经是resolved
,直接执行添加微任务回调.
4.接着是first.then的微任务回调
这里的关键点是 resolve(2)的执行虽然改变了first的内部状态和结果,但是轮到它的then 执行,还需要它内部肚子里的p.then执行完毕之后才能轮到执行.
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
这道题的考察点有两个:
- await 后面的Promise 状态并没有改变,所以造成后面的代码都不会执行
- 值穿透 Promise.resolve(1)的值 在后面传递的时候,后面的回调如果不是函数,都将是无效的.
26.考察点:综合考察
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)
})
输出结果:
resovle1
finally undefined
timer1
Promise{<resolved>: undefined}
考察点:
- finally 的回调并没有参数,同时它是then的特例,同样会加入微任务队列中
- Promise的状态只能改变一次
27.考察点: node
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
考察点:
把process.nextTick
当做微任务执行就可以了.
宏任务中的微任务,只和自己有关,两个宏任务中的微任务,并不会搅合,不需要考虑那么多.
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
这道题相对于上一道反而简单.
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
考察点:
宏任务和微任务的关系
Promise.resolve().then()
添加一个微任务.
30.考察点:综合考察
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
在这道题目中,我们需要知道,无论是thne还是catch中,只要throw 抛出了错误,就会被catch捕获,如果没有throw出错误,就被继续执行后面的then。
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
考察点:
这里的疑惑点就是resolve
执行的时候,虽然会改变Promsie对象的状态和值,同时触发它的回调.
但是这里它的回调还没有,因为它是同步执行的,后面then
的代码还没有来得及执行.
所以需要它的同步代码全都执行完毕,才能执行后面的then
方法来添加回调函数.
32.考察Promise
Promise.resolve(1).then(()=>{
console.log(1)
}).then(()=>{
console.log(2)
}).then(()=>{
console.log(3)
}).then(()=>{
console.log(4)
})
Promise.resolve(1).then(()=>{
console.log(5)
}).then(()=>{
console.log(6)
}).then(()=>{
console.log(7)
}).then(()=>{
console.log(8)
})
输出结果:
1
5
2
6
3
7
4
8
这道题的重点是:Promise.then
它的方法是放入微任务队列执行的.
所以两个代码就是依次放入微任务队列.变成了交替打印的效果.