文章目录
异步
1 event loop(事件循环/事件轮询)
JS 是单线程运行的
异步要基于回调来实现
event loop 就是异步回调的实现原理
1.1 JS 如何执行
-
从前到后,一行一行执行。如果某一行报错,则停止下面代码的执行。
-
先把同步代码执行完,再执行异步。异步的实现需要考虑到回调。
console.log('Hi')
setTimeout(function cb1() {
console.log('cb1') //cb 即 callback
},5000)
console.log('Bye')
1.2 event loop 过程
- 同步代码,一行一行放在
Call Stack(调用栈)
执行,执行完毕后会自动清空。 - 遇到异步,会先 “记录”下来,等待时机(定时结束、网络请求返回等)
- 时机到了,就将之前“记录”的内容移动到
Callback Queue
中。 - 如
Call Stack
为空(即同步代码执行完)Event Loop
开始工作,即Event Loop
开始轮询查找Callback Queue,
如Callback Queue
中有内容则移动到Call Stack
中执行。 - 如果
callback queue
中没有内容,则Event Loop
会一直轮询查找(像没关机的手机一样,有电话就接,没电话是就一直开机看有没有电话)
这里所说的event loop 一直轮询的意思是,在call stack 清空后被触发,然后已知轮询 callback queue,当callback queue 中有内容时,将内容传给 call stack,此时call stack 中有内容,因此,轮询暂停,待call stack 没内容时再继续轮询。
console.log('a')
setTimeout(function fnc() {
console.log('c1')
}, 5000)
console.log('b')
1.3 异步基于 event loop
setTimeout 、 ajax 等异步操作使用回调,基于 event loop
DOM 事件也使用回调,基于 event loop (DOM事件并不是异步,虽然基于的原理相同)
2 promise 进阶
三种状态: pending(正在进行中) resolved rejected
pending -> resolved; pending -> rejected
状态的表现和变化:
pending 状态,不会触发 then 和 catch
resolved 状态,会触发后续的 then 回调函数
rejected 状态,会触发后续的 catch 回调函数
then 和 catch 对状态的影响
then 正常返回 resolved ,里面有报错则返回 rejected
catch 正常返回 resolved,里面有报错则返回 rejected
2.1 then 和 catch 改变状态
- then 正常返回 resolved ,里面有报错则返回 rejected
const p1 = Promise.resolve().then(() => {
return 100
})
console.log('p1',p1) //resolved ,打印显示的是pending,因为异步的原因,点开Promise对象,可以看出是 resolved
p1.then(() => {
console.log('123')//*
})
const p2 = Promise.resolve().then(() => {
throw new Error('then error')
})
p2.then(() => {
console.log('456')
}).catch(err => {
console.error('error100',err)//*
})
console.log('p2',p2) // rejeced 触发后续 catch 回调
-
catch 正常返回 resolved,里面有报错则返回 rejected(得先由 rejected 状态触发catch,才有这个结论)
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(101) }).catch(err => { console.error('error101',err) // resolved 因为此处是catch 返回的promise })
举例:
// 其一
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')
}).catch(() => {
console.log(2) //2
}).then(() => {
console.log(3) //3
})
//其三
Promise.resolve().then(() => {
console.log(1) //1 rejeced
throw new Error('erro1')
}).catch(() => {
console.log(2) //2 resolved
}).catch(() => {
console.log(3)
})
3 async/await
3.1 背景
- 异步回调 callback hell
- Promise then catch 链式调用,但也是基于回调函数
- async/await 是同步语法,彻底消灭回调函数(同步语法)
; (async function ( ){
const img1 = await loadImg(src1)
console.log(img1.height,img1.width)
})()
async function loadImg1() {
const img1 =await loadImg(src1)
return img1
}
3.2 async/await 和 Promise 的关系
async/await 是消灭异步回调的终极武器,但和Promise并不互斥,反而,两者相辅相成。
- 执行async函数,返回的是 Promise 对象
- await 相当于 Promise 的 then
- try…catch 可捕获异常,代替了 Promise 的 catch
async function fn1() {
//return 100 //相当于return Promise。resolve(100)
return Promise.resolve(200)
}
const res1 = fn1() //执行 async 函数,返回的是一个 Promise 对象
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)
})()
-
await 后面跟Promise对象,如果是数字可以转换成
Promise.resolve(数字)
的形式(自动的,不需要我们操作),因为,async 函数返回的是 Promise 对象,也可以将此类函数放在 await 之后。
;(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
}
})()
特殊:
;(async function () {
const p4 = Promise.reject('err1') //rejected状态
const res = await p4 //这里等同于 Promise.reject('err1').then()
console.log(res) //没结果的,resolved状态 才能触发 then
})
3.3 异步的本质
- async/await 是消灭异步回调的终极武器
- JS 还是单线程,还得是有异步,还得是基于 event loop
- async/await 只是一个语法糖
- await 后面都可以看做是 callback 里的内容,执行时需要按照异步顺序执行。
async function async1 () {
console.log('async1 start') // 2
await async2() //async2 return 的是一个undefined
// await 后面都可以看做是 callback 里的内容,即异步。后面的内容相当于计时器里的内容,基于event loop
console.log('async1 end')// 5
}
async function async2() {
console.log('async2')//3
}
console.log('script start') //1
async1()
console.log('script end') // 4
4 微任务/宏任务
宏任务(macroTask) 和 微任务(micro Task)
宏任务:setTimeout,setInterval, Ajax, DOM事件(不是异步)
微任务:Promise async/await
微任务执行时机比宏任务要早!!!
console.log(100)
//宏任务
setTimeout(() => {
console.log(200)
})
setTimeout(() => {
console.log(201)
})
// 如果有两个宏任务,按照进入Callback queue 的顺序,先进先出(在定时相同的情况下)
// 微任务
Promise.resolve().then(() => {
console.log(300)
})
console.log(400)
// 100 400 300 200 201
4.1 event loop 和 DOM 渲染
在代码执行过程中有DOM操作时,event loop的触发前提:1.call stack 空闲;2.DOM 渲染完毕;3.触发 event loop,轮询 callback queue 队列
- 每次 Call Stack 清空(即每次轮询结束),即同步任务执行完。
- 都是 DOM 重新渲染的机会,DOM 结构如有改变则重新渲染。
- 然后再次触发下一次的 Event loop
const $p1 = $('<p>1234</p>')
const $p2 = $('<p>1234</p>')
const $p3 = $('<p>1234</p>')
$('#container')
.append($p1)
.append($p2)
.append($p3)
console.log('length',$('#container').children().length) //3
alert('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染')
// alert 会阻断 js 执行,也会阻断 DOM 渲染,便于查看效果
- 宏任务: DOM 渲染后触发,如 setTimeout
- 微任务: DOM渲染前触发, 如Promise
// 微任务:DOM 渲染后出发,如 setTimeout
Promise.resolve().then(() => {
console.log('length1',$('#container').children().length)
alert('Promise then') // DOM 还没有渲染
})
// 宏任务: DOM 渲染后触发
setTimeout(() => {
console.log('length2', $('#container').children().length)
alert('setTimeout') //DOM 已经渲染了
})
4.2 微任务和宏任务的根本区别
微任务是 ES6
语法规定的(存放在微任务队列中),先于DOM渲染。宏任务是由浏览器规定的 。