目录
准备
在做这类题目之前,你应该清楚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()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
好啦!本文到这里就结束了,你做对了几道?