event loop (事件循环/事件轮询)
- JS是单线程运行的
- 异步要基于回调来实现
- event loop 就是异步回调的实现原理 JS如何执行?
- 从前到后,一行一行执行
- 如果某一行执行报错,则停止下面代码的执行
- 先把同步代码执行完,再执行异步
console.log('Hi')
setTimeout(function cb1() {
console.log('cb1') // cb 即 callback
})
console.log('Bye')
// 'Hi'
// 'Bye'
// 'cb1'
- Call Stack 调用栈
- Web APIs 浏览器api(es之外的)
- Event Loop 事件轮询/事件循环
- Callback Queue 回调函数队列
总结event loop过程1
- 同步代码,一行一行放在Call Stack 执行
- 遇到异步,会先“记录”下,等待时机(定时、网络请求等)
- 时机到了,就移动到 Callbanck Queue 总结event loop过程2
- 如 Call Stack 为空(即同步代码执行完)Event Loop 开始工作
- 轮询查找 Callback Queue,如有则移动到 Call Stack 执行
- 然后继续轮询查找(永动机一样) DOM 事件和event loop
- JS是单线程的
- 异步(setTimeout,ajax 等)使用回调,基于event loop
- DOM 事件也使用回调,基于 event loop(DOM 事件不是异步,立马执行,页面触发)
Promise
- 三种状态:pending resolved rejected
- 状态的表现和变化:pending —> resolved 或 pending —> rejected,变化不可逆
- then 和 catch 对状态的影响:pending 状态,不会触发 then 和 catch;resolved 状态,会触发后续的 then 回调函数;rejected 状态,会触发后续的 catch 回调函数
Promise.resolve()、Promise.reject()
then 和 catch 改变状态: - then 正常返回 resolved,里面有报错则返回rejected
- 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') }) // 可打印100
const p4 = Promise.reject('my error').catch(err => {
throw new Error('catch error')
})
console.log('p4', p4) // rejected 触发 catch 回调
p4.then(() => { console.log('200') }) // 不可打印
.catch(() => { console.log('some err') }) // 可打印 resolved状态
// 第一题
Promise.resolve().then(() => {
console.log(1) // 1
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3) // 3
}) // resolved
// 第二题
Promise.resolve().then(() => {
console.log(1) // 1
throw new Error('error')
}).catch(() => {
console.log(2) // 2
}).then(() => {
console.log(3) // 3
}) // resolved
// 第三题
Promise.resolve().then(() => { // rejected 触发 catch 回调
console.log(1) // 1
throw new Error('error')
}).catch(() => { // resolved 触发 then 回调
console.log(2) // 2
}).catch(() => {
console.log(3) // 注意这里是catch
})
async/await
- 异步回调 callback hell
- Promise then catch 链式调用,但也是基于回调函数 async/await 是同步语法,彻底消灭回调函数
// 加载图片
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.scr = src
})
return p
}
(async function () { // 同步写法 执行异步代码
const img1 = await loadImg(src1)
console.log(img1.width)
const img2 = await loadImg(src2)
console.log(img2.width)
})
// 另一种写法
async function loadImg1() {
const img1 = await loadImg(src1)
return img1
}
async function loadImg2() {
const img2 = await loadImg(src2)
return img2
}
(async function () {
const img1 = await loadImg1()
console.log(img1.width)
const img2 = await loadImg2()
console.log(img2.width)
})
async/await 和 Promise 的关系
- async/await 是消灭异步回调的终极武器
- 但和Promise并不互斥
- 反而,两者相辅相成
- 执行async 函数,返回的是Promise 对象
- await 相当于 Promise 的 then
- try...catch 可捕获异常,代替了Promise 的 catch
// 第一例
async function fn1() {
return 100 // 相当于 return Promise.resolve(100)
}
const res1 = fn1() // 执行async函数,返回的是一个 Promise对象
console.log('res1', res1) // Promise对象
res1.then(data => {
console.log('data', data) // 100
})
// 第二例
!(async function() {
const p1 = Promise.resolve(300)
const data = await p1 // await 相当于 Promise的then
console.log('data', data)
})()
// 第三例
!(async function() {
const data = await 400 // await Promise.resolve(400)
console.log('data', data) // 400
})()
// 第四例
!(async function() {
const data = await fn1()
console.log('data', data) // 100
})()
// 第五例,需要用try..catch捕获
!(async function() {
const p4 = Promise.reject('err') // rejected状态
const res = await p4 // await -> then,因为上一步是rejected状态,所以不会执行
console.log(res) // 不会打印
})()
try..catch
!(async function() {
const p4 = Promise.reject('err') // rejected状态
try {
const res = await p4
console.log(res)
} catch (ex) {
console.error(ex) // try..catch 相当于 Promise catch
}
})()
异步的本质
- async/await 是消灭异步回调的终极武器
- JS还是单线程,还得是有异步,还得是基于 event loop
- async/await只是一个语法糖,但这颗糖真香!
// 函数定义完不会立即执行
async function async1 () {
console.log('async1 start') // 2 重要
await async2() // 先执行async2,再进行await操作
// await 的后面,都可以看作是 callbank 里的内容,即异步
console.log('async1 end') // 5
// 相当于Promise.resolve().then(() => {console.log('async1 end')})
}
async function async2 () {
console.log('async2') // 3 重要
}
console.log('script start') // 1
async1() // 执行函数,立即执行里面内容,还没到异步
console.log('script end') // 4 同步代码已执行完,去执行异步
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.log(res)
}) // 一次性打印出来了,一瞬间执行了三次
// 异步循环
!(async function(){
for (let i of nums) {
const res = await muti(i)
console.log(res)
}
})()
宏任务 macroTask 和微任务 microTask
什么是宏任务,什么是微任务:
- 宏任务: setTimeout,setInterval,Ajax,DOM 事件
- 微任务: Promise async/await
- 微任务执行时机比宏任务要早
event loop 和 DOM 渲染:
- 再次回归一遍 event loop 的过程
- JS 是单线程的,而且和 DOM 渲染共用一个线程
- JS 执行的时候,得留一些时机供 DOM 渲染
- 每次 Call Stack 清空(即每次轮询结束),即同步任务执行完
- 都是 DOM 重新渲染的机会,DOM 结构如有改变则重新渲染
- 然后再去触发下一次 Event Loop
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container').append($p1)
.append($p2)
.append($p3)
console.log('length', $('#container').children().length)
alert('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染')
// alert 会阻断 js 执行,也会阻断 DOM 渲染,便于查看效果
微任务和宏任务的区别:
- 宏任务: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.log('length1', $('#container').children().length)
alert('Promise then') // DOM 渲染了吗---no
})
// 宏任务:DOM 渲染后触发
setTimeout(() => {
console.log('length2', $('#container').children().length)
alert('setTimeout') // DOM 渲染了吗---yes
})
从 event loop 解释,为何微任务执行更早: 为什么?
- 微任务是ES6语法规定的
- 宏任务是由浏览器规定的
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 () { // 宏任务 setTimeout
console.log('setTimeout') // 8
}, 0)
async1()
// 初始化 promise 时,传入的函数会立刻被执行
new Promise(function () {
console.log('promise1') // 4
resolve()
}).then (function () { // 微任务
console.log('promise2') // 7
})
console.log('script end') // 5
// 同步代码执行完毕(event loop -call stack 被清空)
// 执行微任务
// (尝试触发 DOM 渲染)
// 触发 Event Loop
// 执行宏任务