uploadhub的技术博客 uploadhub的技术博客
首页
  • 学习笔记

    • 《HTML5和CSS3篇》
    • 《JavaScript基础篇》
    • 《JavaScript高级篇》
    • 《Ajax篇》
    • 《JavaScript模块化篇》
    • 《Node.js篇》
    • 《MongoDB篇》
    • 《Promise篇》
    • 《Git篇》
  • 《Vue2+Vue3篇》
  • 《React篇》
  • 一面-基础
  • 二三面-进阶
关于我
  • 分类
  • 标签
  • 归档

uploadhub

首页
  • 学习笔记

    • 《HTML5和CSS3篇》
    • 《JavaScript基础篇》
    • 《JavaScript高级篇》
    • 《Ajax篇》
    • 《JavaScript模块化篇》
    • 《Node.js篇》
    • 《MongoDB篇》
    • 《Promise篇》
    • 《Git篇》
  • 《Vue2+Vue3篇》
  • 《React篇》
  • 一面-基础
  • 二三面-进阶
关于我
  • 分类
  • 标签
  • 归档
  • JS基础知识(一)-变量类型和计算
  • JS基础知识(二)- 原型和原型链
  • JS基础知识(三)- 作用域、闭包
  • JS基础知识(四)- 异步和单线程
  • JS基础知识(五)- 异步进阶
    • 1.几道面试题(以点带面)
    • 2.涉及知识点
      • 2.1 Event Loop(事件循环/事件轮询)
      • 总结 Event Loop 过程
      • DOM 事件和 Event Loop
      • 2.2 Promise进阶
      • 三种状态
      • 状态的表现
      • then 和 catch 改变状态
      • 2.3 async/await
      • async/await 和 Promise 的关系
      • 异步的本质
      • 2.4 for ... of
      • 2.5 宏任务和微任务
      • 二者分类
      • Event Loop 和 DOM 渲染
      • 微任务和宏任务的区别
      • 从 Event Loop 解释,为何微任务执行更早
    • 3.对前文问题的解答
      • 3.1 描述 Event Loop 机制(可画图)
      • 3.2 什么是宏任务和微任务,两者区别
      • 3.3 Promise 有哪三种状态?如何变化?
      • 3.4 场景题-Promise then 和 catch 的连接
      • 3.5 场景题-async/await语法
      • 3.6 场景题-Promise和setTimeout的顺序
      • 3.7 场景题-外加 async/await 的顺序问题
  • JS-Web-API(一)- DOM & BOM
  • JS-Web-API(二)- 事件
  • JS-Web-API(三)- Ajax
  • JS-Web-API(四)- 存储
  • HTTP协议及缓存机制
  • 开发环境
  • 运行环境
  • 一面-基础
uploadhub
2022-05-28
目录

JS基础知识(五)- 异步进阶

# 1.几道面试题(以点带面)

1.请描述 event loop(事件循环/事件轮询)的机制,可画图

2.什么是宏任务和微任务,两者有什么区别?

3.Promise 有哪三种状态?如何变化?

4.场景题-Promise then 和 catch 的连接

//第一题
Promise.resolve().then(()=>{
	console.log(1)
}).catch(()=>{
	console.log(2)
}).then(()=>{
	console.log(3)
})
1
2
3
4
5
6
7
8
//第二题
Promise.resolve().then(()=>{
	console.log(1)
	throw new Error('erro1')
}).catch(()=>{
	console. log(2)
}).then(()=>{
	console.log(3)
})
1
2
3
4
5
6
7
8
9
//第三题
Promise.resolve().then(()=>{
	console. log(1)
	throw new Error('erro1')
}).catch(()=>{
	console.log(2)
}).catch(()=>{//这里是 catch
	console.log(3)
})
1
2
3
4
5
6
7
8
9

5.场景题-async/await语法

//第一题
async function fn(){
	return 100
}
(async function(){
	const a = fn ( ) // ? ?
	const b = await fn()//??
})()
1
2
3
4
5
6
7
8
//第二题
(async function(){
	console. log('start')
	const a = await 100
	console.log('a',a)
	const b = await Promise . resolve ( 200 )
	console. log('b', b)
	const c = await Promise.reject(300)
	console. log('c', c)
	console. log('end')
})()//执行完毕,打印出那些内容?
1
2
3
4
5
6
7
8
9
10
11

6.场景题-Promise和setTimeout的顺序

console. log(100)
setTimeout(()=>{
	console. log(200)
})
Promise.resolve().then(()=>{
	console.log(300)
})
console.log(400)
1
2
3
4
5
6
7
8

7.场景题-外加 async/await 的顺序问题

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')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 2.涉及知识点

  1. Event Loop

  2. Promise 进阶

  3. async/await

  4. 微任务/宏任务

# 2.1 Event Loop(事件循环/事件轮询)

  1. JS是单线程运行的
  2. 异步要基于回调来实现
  3. Event Loop 就是异步回调的实现原理

JS代码如何执行

  1. 从前到后,一行一行执行
  2. 如果某一行执行报错,则停止下面代码的执行
  3. 先把同步代码执行完,再执行异步

示例代码

console.log('Hi')
setTimeout(function cb1(){
	console.log(apos;cb1')//cb即callback
},5000)
console.log('Bye')
1
2
3
4
5

下面我们借示例代码来分析Event Loop的具体机制

①从第一行代码开始执行,把它推入调用栈(Call Stack)中,由调用栈负责执行该代码,执行时控制台打印"Hi"

异步进阶示意图-1

②此时第一行代码执行完毕,清空调用栈

异步进阶示意图-2

③然后执行第二段代码,前文说过setTimeout是浏览器制定的标准Web API,而不是ECMA标准,所以Web APIs的栈里就多了一个5秒的定时器Timer,里面存放着setTimeout的第一个参数,也就是回调函数cb1

异步进阶示意图-3

④此时调用栈执行第二段代码完毕,清空调用栈,然后马上再执行第三段代码,将其推入栈中执行,浏览器控制台打印"Bye"

异步进阶示意图-4

⑤此时调用栈已清空,且所有同步代码执行完毕,所以Event Loop机制会马上启动(具体由浏览器内核管理),该机制会不停轮询,查看回调队列(Callback Queue)中有没有新的代码,有则将其推入调用栈执行。具体到示例,则是5秒钟后,定时器将回调函数cb1准时推入回调队列中

异步进阶示意图-5

⑥此时Event Loop轮询发现回调函数中有新的代码,于是立即将其推入调用栈,调用栈直接执行回调函数cb1,由于cb1函数体内另有代码,所以调用栈的栈顶又多加了层console.log('cb1'),执行后控制台输出"cb1"

异步进阶示意图-6

⑦此时所有同步代码又一次全部执行完毕,调用栈重新清空,到此整个示例代码执行结束

异步进阶示意图-7

# 总结 Event Loop 过程

  1. 同步代码,一行一行放在Call Stack执行
  2. 遇到异步,会先“记录”下,等待时机(定时、网络请求等)
  3. 如Call Stack为空(即同步代码执行完)Event Loop开始工作
  4. 轮询查找 Callback Queue,如有则移动到 Call Stack 执行
  5. 然后继续轮询查找(永动机一样)

# DOM 事件和 Event Loop

  1. JS 是单线程的
  2. 异步(setTimeout,ajax等)使用回调,基于Event Loop
  3. DOM 事件也使用回调,也基于Event Loop机制(但DOM事件并不算异步)

# 2.2 Promise进阶

需要把握的三个要点:

  1. Promise的三种状态
  2. 状态的表现和变化
  3. then 和 catch 对状态的影响

# 三种状态

  1. pending resolved rejected
  2. pending -> resolved 或 pending -> rejected
  3. 变化不可逆
const p1 = new Promise((resolve, reject) =>{})
console.log('p1', p1) // pending

const p2 = new Promise((resolve, reject) =>{
	setTimeout(()=>{
		resolve()
	})
})
console.log('p2',p2)// pending 一开始打印时
setTimeout(()=> console. log('p2-setTimeout', p2))// resolved

const p3 = new Promise((resolve, reject) =>{
	setTimeout(()=>{
		reject()
	})
})
console.log('p3', p3)//一开始仍是pending,控制台展开后发现变成了rejected
setTimeout(()=> console. log('p3-setTimeout', p3))// rejected
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 状态的表现

  1. pending状态,不会触发then和catch
  2. resolved 状态,会触发后续的 then 回调函数
  3. rejected 状态,会触发后续的 catch 回调函数

# then 和 catch 改变状态

  1. then 正常返回 resolved,里面有报错则返回 rejected
  2. catch 正常返回 resolved,里面有报错则返回 rejected
/*本段代码演示:then 正常返回 resolved,里面有报错则返回 rejected*/
const p1 = Promise.resolve().then(()=>{
	return 100//此时正常返回resolved状态
})
// resolved 会触发后续 then 回调
p1.then(()=>{
	console.log('123')
})

const p2 = Promise.resolve().then(()=>{
	throw new Error('then error')//此时返回rejected状态
})
// rejeced触发后续 catch 回调
p2.then(()=>{
	console.log('456')
}).catch(err => {
	console.error('err100',err)//于是只执行了这里的代码,then的回调函数不进入执行
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*本段代码演示:catch 正常返回 resolved,里面有报错则返回 rejected*/
const p3 Promise.reject('my error').catch(err =>{
	console.error(err)
})
console.log('p3',p3)//resolved。注意!触发then回调
p3.then(()=>{
	console.log(100)
})
const p4=Promise.reject('my error').catch(err =>{
	throw new Error('catch err')
})
console.log('p4',p4)//rejected。触发 catch回调
p4.then(()=>{
	console.log(200)
}).catch()=>{
	console. log('some err')
})// p4执行完毕,状态最终返回的是resolved
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 2.3 async/await

  1. 异步回调 callback hell
  2. Promise then catch 链式调用,但也是基于回调函数
  3. async/await是同步语法,彻底消灭回调函数

# async/await 和 Promise 的关系

  1. async/await是消灭异步回调的终极武器
  2. 但和 Promise 并不互斥,反而二者相辅相成
  3. 执行async函数,返回的是Promise对象
  4. await 相当于 Promise 的 then
  5. try...catch 可捕获异常,代替了 Promise 的 catch

示例代码

async function fn1(){
	// return 200//相当于 return Promise.resolve(200)
	return Promise.resolve(200)
}

const res1=fn1()//执行 async 函数,返回的是一个 Promise 对象
// console. log('res1', res1)// Promise 对象
res1.then(data =>{
	console.log('data',data)//200
})
1
2
3
4
5
6
7
8
9
10
/*下面三段代码主要演示三种实质结果一样的情况:await 相当于 Promise 的 then*/
!(async function(){
	const p1=Promise.resolve(300)
	const data = await p1 // await 相当于 Promise then
	console.log('data', data)//打印300
})()
!(async function (){
	const datal = await 400 // 相当于await Promise.resolve(400)
	console.log('datal', data1)//打印400
})()
!(async function (){
	const data2 = await fn1()//await后面跟了个async函数,也就相当于跟了个Promise
	console.log('data2', data2)//打印200
})()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
!(async function (){
	const p4=Promise.reject('err1')//rejected状态
	try {
		const res = await p4
		console.log(res)
	} catch (ex) {
		console.error(ex)//try..catch相当于promise catch
    }
})()


/*这段代码演示了Promise状态不为resolved,await无能为力的特殊情形*/
/*这也说明了try..catch语法和await配合的必要性*/
!(async function()
	const p4=Promise.reject('err1')//rejected 状态
	const res = await p4 // await -> then
	console.log('res', res)//该行代码未被执行
)()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 异步的本质

  1. async/await是消灭异步回调的终极武器
  2. JS还是单线程,还得是有异步,还得是基于event loop
  3. async/await只是一个语法糖,但这颗糖真香!
async function async1(){
	console.log('async1 start')//第2步打印
	await async2()// 执行结果是undefined
    // await 的后面,都可以看做是 callback 里的内容,即异步
	//类似于event loop,setTimeout(cb1)。但setTimeout是宏任务
    //实际await后面的代码应该等于微任务,即类似于Promise.then,后面会讲
    //setTimeout(function(){console.log('async1 end')})
	//Promise.resolve().then( ()=>{console.log('async1 end')} )
    
	console.log('async1 end')//第5步打印
}

async function async2 (){
	console.log('async2')//第3步打印
}

console.log('script start')//第1步打印
async1()
console. log('script end')//第4步打印
//此时同步代码已经执行完(event loop机制,开始执行异步任务)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function async1(){
	console. log('async1 start')//2
	await async2()
	//下面三行都是异步回调 callback 的内容
	console.log('async1 end')//5
	await async3()
	//下面一行是异步回调的内容
	console. log('async1 end 2')//7
}

async function async2 (){
	console. log('async2')//3
}

async function async3(){
	console.log('async3')//6
}

console.log('script start')//1
async1()
console. log('script end')//4
//同步代码执行完 event loop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 2.4 for ... of

  1. for...in(以及forEach、for)是常规的同步遍历
  2. for...of 常用于异步的遍历
 function muti(num) {
 	return new Promise((resolve) => {
    	setTimeout(() => {
        	resolve(num * num);
        }, 1000);
    });
}

var arr = [1, 2, 3];
//如果使用foreach遍历处理异步代码不会等待异步代码的执行,一次输出所有异步结果
//   arr.forEach(async (value, index, array) => {
//     // console.log(value);
//     const val = await muti(value);
//     console.log(val);
//   });

//如果使用for...of可以使异步按先后顺序执行
!(async function () {
	for (let a of arr) {
    	const ans = await muti(a);
        console.log(ans);
    }
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 2.5 宏任务和微任务

宏任务:MacroTask,微任务:MicroTask

需要把握的三个要点:

  1. 什么是宏任务,什么是微任务
  2. event loop 和 DOM 渲染
  3. 微任务和宏任务的区别

# 二者分类

  1. 宏任务:setTimeout,setInterval,Ajax,DOM事件
  2. 微任务:Promise async/await
  3. 微任务执行时机比宏任务要早

# Event Loop 和 DOM 渲染

  1. JS是单线程的,而且和DOM渲染共用一个线程
  2. JS执行的时候,得留一些时机供DOM渲染

回顾2.1节Event Loop过程的示例代码,此时我们可以增加 DOM 渲染时机,并给出如下示意图

异步进阶示意图-8

总结

  1. 每次 Call Stack清空(即每次轮询结束),即同步任务执行完
  2. 都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
  3. 然后再去触发下一次 Event Loop

# 微任务和宏任务的区别

  1. 宏任务:DOM 渲染后触发,如 setTimeout
  2. 微任务:DOM渲染前触发,如Promise

# 从 Event Loop 解释,为何微任务执行更早

  1. 微任务有个专门的队列micro task queue,它和宏任务队列,也就是图中所示Callback queue是分开的
  2. 这是因为微任务是ES6语法规定的,宏任务是由浏览器规定的
异步进阶示意图-9 异步进阶示意图-10

# 3.对前文问题的解答

# 3.1 描述 Event Loop 机制(可画图)

  1. 自行回顾前文 Event Loop 的过程
  2. 和DOM渲染的关系(可以分段,逐步讲,不然有点乱)
  3. 微任务和宏任务在 Event Loop 过程中的不同处理

# 3.2 什么是宏任务和微任务,两者区别

  1. 宏任务:setTimeout,setInterval,Ajax,DOM事件
  2. 微任务:Promise,async/await
  3. 微任务执行时机比宏任务要早

# 3.3 Promise 有哪三种状态?如何变化?

  1. pending resolved rejected
  2. pending -> resolved 或 pending -> rejected
  3. 变化不可逆

# 3.4 场景题-Promise then 和 catch 的连接

//第一题
Promise.resolve().then(()=>{
	console.log(1)//第一步执行,打印1
}).catch(()=>{
	console.log(2)//不进入执行
}).then(()=>{
	console.log(3)//第二步执行,打印3
})

//第二题
Promise.resolve().then(()=>{
	console.log(1)//第一步执行,打印1
	throw new Error('erro1')//第二步执行,变成rejected状态
}).catch(()=>{
	console.log(2)//第三步执行,打印2,变成resolved状态
}).then(()=>{
	console.log(3)//第四步执行,打印3,继续resolved状态
})

//第三题
Promise.resolve().then(()=>{
	console.log(1)//第一步执行,打印1
	throw new Error('erro1')//第二步执行,变成rejected状态
}).catch(()=>{
	console.log(2)//第三步执行,打印2,变成resolved状态
}).catch(()=>{//这里是 catch
	console.log(3)//不执行
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 3.5 场景题-async/await语法

//第一题
async function fn(){
	return 100
}
(async function(){
	const a = fn () // 结果返回Promise,值是100
	const b = await fn()//await相当于Promise的then,所以b就等于100
})()
    
    
//第二题
(async function(){
	console.log('start')
	const a = await 100//相当于直接返回值
	console.log('a',a)//打印100
	const b = await Promise.resolve (200)//牢记await相当于Promise的then方法
	console. log('b', b)//打印100
	const c = await Promise.reject(300)//这里直接报错,后续代码都不会执行
	console.log('c', c)
	console.log('end')
})()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 3.6 场景题-Promise和setTimeout的顺序

console.log(100)//第一步执行,打印100
setTimeout(()=>{
	console.log(200)//第四步执行,打印200
})
Promise.resolve().then(()=>{
	console.log(300)//第三步执行,打印300
})
console.log(400)//第二步执行,打印400
1
2
3
4
5
6
7
8

# 3.7 场景题-外加 async/await 的顺序问题

async function async1(){
	console.log('async1 start')//第2步执行
	await async2()
    //await后面的代码都作为回调内容-微任务
	console.log('async1 end')//第6步执行
}
async function async2(){
	console.log('async2')//第3步执行
}
console.log('script start')//第1步执行

setTimeout(function(){
	console.log('setTimeout')//宏任务,第8步执行
},0)

async1()

//初始化Promise时,传入的函数会被立即执行
new Promise(function(resolve){
	console.log('promise1')//第4步执行
	resolve()
}).then(function(){
	console.log('promise2')//微任务,第7步执行
})

console.log('script end')//第5步执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#面试#异步#Promise
JS基础知识(四)- 异步和单线程
JS-Web-API(一)- DOM & BOM

← JS基础知识(四)- 异步和单线程 JS-Web-API(一)- DOM & BOM→

最近更新
01
HTTP协议及缓存机制
05-28
02
开发环境
05-28
03
JS基础知识(一)-变量类型和计算
05-28
更多文章>
Theme by Vdoing | Copyright © 2021-2023 uploadhub | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式