异步/事件循环-题目&题解(9.12-31题)

60 阅读15分钟

写在前面:本帖用于复习异步/事件循环的编程题目,验证阶段性的学习成果,欢迎大家批评指正!

事件循环:js在执行代码的过程中,会优先执行执行栈中的同步代码,当遇到异步代码时,会将异步代码放入任务队列中。当执行栈执行完毕后,再从任务队列中提取代码并执行。任务队列又分为宏任务队列与微任务队列,执行顺序为先微后宏。

知识点:

  1. 宏任务队列:setTimeOut/setInternal
  2. 微任务队列:promise中的then/catch;async/await
  3. 执行顺序为先微后宏
  4. await会阻塞当前的代码执行,阻塞代码块中剩余未执行的语句,可以看作将后续代码放入微任务队列中。
  5. promsie值穿透问题,当链式调用的过程中,本层没有返回结果,会默认将上层传递的值向下传递。
  6. then和catch会捕获异常,当异常被捕获后就不会继续向下传递,当二者同时存在,按照先后顺序竞争。
  7. 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-123-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-67-12

流程2:先微后宏

1-6:执行代码2,打印promise1。执行3-5,添加到宏任务队列。此时的宏任务队列为:

7-12
3-5

7-12:执行代码8,微任务执行完成,开始执行宏任务,打印timer1。执行9-11,添加到微任务队列。此时的任务队列:

9-113-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-81-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-710-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

413-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-155-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-104
1320-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-132-5

流程2:先微后宏

9:打印resolve1

10-12:放入宏任务,将finally放入微任务

142-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-173-14
21-2325-36

流程2:

15-17:打印6

21-23:由于p的状态改变,打印8

3-14:开始宏任务

4:打印2

5-7:放入微任务

9:打印4,改变了内部p的状态

12:放入微任务

5-725-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

103-5
12-17
19-21

流程2:先微后宏

10:then状态改变,打印4

3-5:打印2

12-17:打印5

15:新p的状态改变

16:放入微任务

1619-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-152-8
17-19

13-15:then状态改变,打印5

2-8:打印2,将6放入微任务

617-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题-----------------------------------------------------------------------------------------------

参考文献:

  1. 前端面试题汇总(www.yuque.com/cuggz/inter…
  2. 前端面试宝典(fe.ecool.fun/)