JS异步 - 进阶
- 之前讲解JS异步,在于初阶的应用
- 本章在于JS异步的原理和进阶
- 对初学者有点难度,尽量深入浅出
本章主要内容
- event loop
- promise进阶
- async/await
- 微任务/宏任务
本章相关面试题
- 请描述event loop(事件循环/事件轮询)的机制,可画图
- 什么是宏任务和微任务,两者有什么区别?
- Promise有哪三种状态?如何变化
场景题
promise then和catch的连接
//第一题
Promise.resolve().then(() => {
console.info(1)
}).catch(() => {
console.info(2)
}).then(() => {
console.info(3)
})
//输出:1 3
//第二题
Promise.resolve().then(() => {
console.info(1)
throw new Error('error1')
}).catch(() => {
console.info(2)
}).then(() => {
console.info(3)
})
//输出:1 2 3
//第三题
Promise.resolve().then(() => {
console.info(1)
throw new Error('error1')
}).catch(() => {
console.info(2)
}).catch(() => { //这里是catch
console.info(3)
})
//输出:1 2
Promise总结
- 三种状态,状态的表现和变化
- then和catch对状态的影响(重要)
- then和catch的链式调用(常考)
async/await语法
- 异步回调 callback hell
- Promise then catch链式调用,但也是基于回调函数
- async/await是同步语法,彻底消灭回调函数
async/await语法
//加载图片a
function loadImg(src) {
const p = new Promise((resolve, reject) => {
const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
const err = new Error(`图片加载失败 ${src}`)
reject(err)
}
img.src = src
})
return p
}
const src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
const src2 = 'https://img3.mukewang.com/szimg/5fc063c709c41a8f05400304.png'
!(async function () {
//img
const img1 = await loadImg(src1)
console.log(img1.height, img1.width)
//img2
const img2 = await loadImg(src2)
console.log(img2.height, img2.width)
})
async/await和Promise的关系
- 执行async函数,返回的是Promise对象
- await相当于Promise的then
try...catch
可捕获异常,代替了Promise的catch
async function fn1() {
return 100 //相当于 return Promise.resolve(100)
}
const res1 = fn1()
console.log(res1) //Promise {<fulfilled>: 100}
res1.then(data => {
console.log('data', data) //200
})
!(async function () {
const p1 = Promise.resolve(300)
const data = await p1 //await相当于Promise then
console.log('data', data) //300
})()
!(async function () {
const data1 = await 400 //相当于封装成 await Promise.resolve(400)
console.log('data1', data1)
})()
!(async function () {
const data2 = await fn1()
console.log('data2', data2)
})()
!(async function () {
const p4 = Promise.reject('err1') //rejected状态
try {
const res = await p4 //await相当于Promise的then
console.log(res)
} catch (ex) {
console.error(ex) //try...catch相当于promise catch
}
})()
(async function () {
const a = fn() //??
const b = await fn() //??
})()
(async function () {
console.info('start')
const a = await 100
console.info('a', a)
const b = await Promise.resolve(200)
console.info('b', b)
const c = await Promise.reject(300)
console.info('c', c)
console.info('end')
})()//执行完毕,打印出那些内容
promise和setTimeout的顺序
console.info(100)
setTimeout(() => {
console.info(200)
})
Promise.resolve().then(() => {
console.info(300)
})
console.info(400)
外加async/await的顺序问题
async function async1 () {
console.info('async1 start')
await async2()
console.info('async1 end')
}
async function async2 () {
console.info('async2')
}
console.info('script start')
setTimeout(function () {
console.info('setTimeout')
}, 0)
async1()
new Promise(function (resolve) {
console.info('promise1')
resolve()
}).then(function () {
console.info('promise2')
})
console.info('script end')
event loop(事件循环/事件轮询)
- JS是单线程运行的
- 异步要基于回调来实现
- event loop就是异步回调的实现原理
JS如何执行?
- 从前到后,一行一行执行
- 如果某一行执行报错,则停止下面代码的执行
- 先把同步代码执行完,再执行异步
console.info('Hi')
setTimeout(function cb1() {
console.info('cb1') //cb即callback
}, 5000)
console.info('Bye')
开始讲解event loop过程
- 初学者可能会感觉难,尽量深入浅出
- 第一遍讲解时,感觉不懂的不要停下,会重复讲三遍
- 不要抠细节,不要扩大范围,核心是event loop的过程
总结event loop过程
- 同步代码,一行一行放在Call Stack执行
- 遇到异步,会先“记录”下,等待时机(定时、网络请求等)
- 时机到了,就移动到Callback Queue
- 如Call Stack为空(即同步代码执行完)Event Loop开始工作
- 轮询查找Callback Queue,如有则移动到Call Stack执行
- 然后继续轮询查找(永动机一样)
DOM事件和event loop
- JS是单线程的
- 异步(setTimeout,ajax等)使用回调,基于event loop
- DOM事件也使用回调,基于event loop
console.info('Hi') //1
setTimeout(function cb1() {
console.info('cb1') //3, cb即callback
}, 5000)
console.info('Bye') //2
<button id="btn1"></button>
<script>
console.info('Hi') //1
$('btn1').click(function (e) {
console.info('button clicked') //3
})
console.info('Bye') //2
</script>
Promise
- 三种状态:
pending
、resolved
、rejected
pending -> resolved
或pending -> rejected
- 变化不可逆
- 状态的表现和变化
- pending状态,不会触发then和catch
- resolved状态,会触发后续的then回调函数
- rejected状态,会触发后续的catch回调函数
- then和catch对状态的影响
const p1 = Promise.resolve(100)
console.info(p1)
p1.then(data => {
console.info(data)
}).catch(err => {
console.info(err)
})
const p2 = Promise.reject('err')
// console.info('p2', p2)
p2.then(data => {
console.info('data2', data)
}).catch(err => {
console.info('err2', err)
})
then和catch改变状态
- then
正常
返回resolved
,里面有报错
则返回rejected
- catch
正常
返回resolved
,里面有报错
则返回rejected
const p1 = Promise.resolve().then(() => {
return 10
})
console.info('p1', p1) //resolved 触发后续 then 回调
p1.then(() => {
console.info('123')
})
const p2 = Promise.resolve().then(() => {
throw new Error('then error')
})
console.info('p2', p2) //reject 触发后续 catch 回调
p2.then(() => {
console.info('456')
}).catch(err => {
consoel.info('err100', err)
})
Promise面试题
- then
正常
返回resolved
,里面有报错
则返回rejected
- catch
正常
返回resolved
,里面有报错
则返回rejected
// 第一题
Promise.resolve().then(() => {
console.log(1)
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
// 1 3
// 第二题
Promise.resolve().then(() => {
console.log(1)
throw new Error('erro1')
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
// 1 2 3
// 第三题
Promise.resolve().then(() => {
console.log(1)
throw new Error('erro1')
}).catch(() => {
console.log(2)
}).catch(() => { // 注意这里是 catch
console.log(3)
})
// 1 2
Promise总结
- 三种状态,状态的表现和变化
- then和catch对状态的影响(重要)
- then和catch的链式调用(常考)
async/await
- 异步回调 callback hell
- Promise then catch 链式调用,但也是基于回调函数
- async/await是同步语法,彻底消灭回调函数
async/await和Promise的关系
-
async/await是消灭异步回调的终极武器
-
但和Promise并不互斥
-
反而,两者相辅相成
-
执行async函数,返回的是Promise对象
-
await相当于Promise的then
-
try…catch可捕获异常,代替了Promise的catch
async function fn1() {
//return 100
return Promise.resolve(200)
}
const res1 = fn1() //执行async函数,返回的是一个promise对象
console.info('res1', res1) //promise对象
res1.then(data => {
console.info('data', data) //200
})
!(async function () { //前面加的'!'表示与上一行代码分割开,防止浏览器当做函数进行处理报错
const p1 = Promise.resolve(300)
const data = await p1 //await 相当于Promise的then
console.info('data', data)
})
异步的本质
- async/await是消灭异步回调的终极武器
- JS还是单线程,还得有异步,还得是基于event loop
- async/await只是一个语法糖,但这颗糖真香!
async function async1 () {
console.info('async1 start') //2 重要
await async2() //undefined
//await的后面,都可以看做是callback里的内容。即异步
//类似,event loop,settim(cb1)
//setTimeout(function() {console.info('async1 end') })
//Promise.resolve().then(() => {console.info('async1 end')})
console.info('async1 end') //5
}
async function async2 () {
console.info('async2') //3 重要
}
console.info('script start') //1
async1()
console.info('script end') //4
//同步代码已经执行完(event loop)
for ... of
for ... in
(以及forEach for)是常规的同步遍历for ... of
常用于异步的遍历
function muti(num) {
return new Promise(resolve => {
setTimeout(() => {
resolve(num * num)
}, 1000)
})
}
const nums = [1, 2, 3]
nums.forEach(async (i) => {
const res = await muti(i)
console.info(res) //1秒后同时打印出:1、4、9
})
!(async function () {
for (let i of nums) {
const res = await muti(i)
console.info(res) //间隔1秒逐个输出:1、4、9
}
})()
async/await总结
- async/await解决了异步回调,是一个很香的语法糖
- async/await和Promise的关系,重要!
- `for…of的使用
宏任务macroTask和微任务microTask
- 什么是宏任务,什么是微任务
- 宏任务:setTimeout、setInterval、Ajax、DOM事件
- 微任务:Promise、async/await
- 微任务执行时机比宏任务要早(先记住)
- event loop和DOM渲染
- 微任务和宏任务的区别
console.info(100) //1
setTimeout(() => {
console.info(200) //4
})
Promise.resolve().then(() => {
console.info(300) //3
})
console.info(400) //2
event loop和DOM渲染
- 再次回归一遍 event loop的过程
- JS是单线程的,而且和DOM渲染共用一个线程
- JS执行的时候,得留一些时机供DOM渲染
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container').append($p1).append($p2).append($p3)
console.info('length', $('#container').children().length)
alert('本次call stack结束,DOM结构已更新,但尚未触发渲染')
//(alert会阻断js执行,也会阻断DOM渲染,便于查看效果)
event loop和DOM渲染
- 每次Call Stack清空(即每次轮询结束),即同步任务执行完
- 都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
- 然后再次触发下一次event loop
微任务和宏任务的区别
- 宏任务:DOM渲染后触发,如setTimeout
- 微任务:DOM渲染前触发,如Promise
- 先演示现象,稍后再追究原理
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container').append($p1).append($p2).append($p3)
//微任务:DOM渲染前触发
Promise.resolve().then(() => {
console.info('length1', $('#container').children().length)
alert('Promise then') //DOM渲染了吗?NO
})
setTimeout(() => {
console.info('length2', $('#container').children().length) //3
alert('setTimeout') //DOM渲染了吗?yes
})
从event loop解释,为何微任务执行更早
为什么?
- 微任务是ES6语法规定的
- 宏任务是由浏览器规定的
微任务和宏任务 - 总结
- 宏任务有哪些?微任务有哪些?微任务触发时机更早
- 微任务、宏任务和DOM渲染的关系
- 微任务、宏任务和DOM渲染,在event looop的过程
async function fn() {
return 100
}
(async function () {
const a = fn() // ??
const b = await fn() // ??
})()
(async function () {
console.info('start')
const a = await 100
console.info('a', a)
const b = await Promise.resolve(200)
console.info('b', b)
const c = await Promise.reject(300)
console.info('c', c)
console.info('end')
})()
promise和setTimeout的顺序
console.info(100)
setTimeout(() => {
console.info(200)
})
Promise.resolve().then(() => {
console.info(300)
})
console.info(400)
外加async/await的顺序问题
async function async1 () {
console.info('async1 start')
await async2()
console.info('async1 end')
}
async function async2 () {
console.info('async2')
}
console.info('script start')
setTimeout(function () {
console.info('setTimeout')
}, 0)
//连接左侧代码,一起阅读
async1()
new Promise (function (resolve) {
console.info('promise1')
resolve()
}).then(function () {
console.info('promise2')
})
console.info('script end')