系列文章目录
JS 异步进阶
什么是 event loop(事件循环/事件轮询)
- JS 是单线程运行的
- 异步要基于回调来实现
- event loop 就是异步回调的实现原理
JS 如何执行?
- 从前到后,一行一行执行
- 如果某一行执行报错,则停止下面代码的执行
- 先把同步代码执行完,再执行异步
event loop 执行过程
event loop 过程 1
- 同步代码,一行一行放在 Call Stack 执行
- 遇到异步,会先记录下来,等待时机(定时、网络请求等)
- 时机到了,就移动到 Callback Queue
event loop 过程 2
- 如果 Call Stack 为空(即同步代码执行完)Event loop 开始工作
- 轮询查找 Callback Queue,如有则移动到 Call Stack 执行
- 然后继续轮询查找(永动机一样)
图解代码执行过程:
console.log('Hi')
setTimeout(function cb1() {
console.log('cb1')
}, 2000)
console.log('Bye')
图片出处:https://coding.imooc.com/lesson/400.html#mid=35171
DOM 事件与 event loop
- JS 是单线程
- 异步(setTimeout,ajax等)使用回调,基于 event loop
- DOM 事件也使用回调,基于 event loop
Promise 三种状态
三种状态
- pending resolve rejected
- pending => resolve 或 pending => rejected
- 变化不可逆
状态的表现
- pending 状态,不会触发 then 和 catch
- resolved 状态,会触发后续的 then 回调函数
- rejected 状态,会触发后续的 catch 回调函数
成功的回调:只执行 then,不会执行 catch
const p1 = Promise.resolve('100')
p1.then(data => {
console.log('data', data)
}).catch(err => {
console.log('err', err)
})
失败的回调:只执行 catch,不执行 then
const p2 = Promise.reject('-100')
p2.then(data => {
console.log('data2', data)
}).catch(err => {
console.log('err2', err)
})
then 和 catch 改变状态
- then 正常返回 resolved,里面有报错则返回 rejected
- catch 正常返回 resolved,里面有报错则返回 rejected
第一种情况:
const p1 = Promise.resolve().then(() => {
return 100
})
console.log('p1', p1);
p1.then(() => {
console.log('200')
})
const p2 = Promise.resolve().then(() => {
throw new Error('then error')
})
console.log('p2', p2)
p2.then(() => {
console.log('300')
}).catch(err => {
console.log('error', err)
})
第二种情况:
const p3 = Promise.reject('my error').catch(err => {
console.log(err)
})
console.log('p3', p3)
p3.then(() => {
console.log(100)
})
const p4 = Promise.reject('my error').catch(err => {
throw new Error('catch err')
})
console.log('p4', p4)
p4.then(() => {
console.log(200)
}).catch(() => {
console.log('some err')
})
Promise 关于 then 和 catch 的面试题
示例 1 :resolve 成功输出 1 后又是一个成功的回调(fulfilled),之后执行 then,不会执行 catch
Promise.resolve().then(() => {
console.log(1)
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
示例 2:resolve 成功输出 1 后报错异常(rejected),之后执行 catch 输出 2 后(fulfilled),执行 then
Promise.resolve().then(() => {
console.log(1)
throw new Error('error1')
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
示例 3:resolve 成功输出 1 后报错异常(rejected),之后执行 catch 输出 2 后(fulfilled),之后就结束了
Promise.resolve().then(() => {
console.log(1)
throw new Error('error1')
}).catch(() => {
console.log(2)
}).catch(() => {
console.log(3)
})
async 和 await 基本使用
- 使用 await,必须有 async 包裹
! 的作用是防止此前代码结尾不加分号
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
document.body.appendChild(img)
})
return p
}
const src = 'xxx.png'
!(async function() {
const img = await loadImg(src)
console.log(img.width, img.height)
})()
async/await 和 Promise 的关系
- async/awiat 是消灭异步回调的终极武器
- 但和 Promise 并不互斥
- 反而,两者相辅相成
- 执行 async 函数,返回的是 Promise 对象
- await 相当于 Promise 的 then
- try…catch 可捕获异常,代替了 Promise 的 catch
示例 1:执行 async 函数,返回的是一个 Promise 对象
async function fn1(){
return Promise.resolve(200)
}
const res1 = fn1()
console.log('res1', res1);
res1.then(data => {
console.log('data', data)
})
示例 2:
async function fn() {
return Promise.resolve(200)
}
!(async function() {
const p1 = Promise.resolve(300)
const data = await p1
console.log('data', data)
})()
!(async function() {
const data1 = await 400
console.log('data1', data1)
})()
!(async function() {
const data2 = await fn()
console.log('data2', data2);
})()
示例 3:try…catch 异常捕获
!(async function() {
const p4 = Promise.reject('error1')
try {
const res = await p4
console.log(res)
} catch (err) {
console.error(err)
}
})()
示例 4:
!(async function() {
const p5 = Promise.reject('err')
const res = await p5
console.log('res', res)
})()
异步的本质
- 异步的本质是回调函数
- async/await 是消灭异步回调的终极武器
- JS 是单线程,还得是有异步,还得是基于 event loop
- async/await 是一个很好的语法糖
示例:await 后面的内容是 异步的
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
async1()
console.log('script end')
输出顺序:
示例 2:注——await 后面的都是异步内容,等主线程执行完才会触发 event loop 机制执行
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
await async3()
console.log('async1 end 2')
}
async function async2() {
console.log('async2')
}
async function async3() {
console.log('async3')
}
console.log('script start')
async1()
console.log('script end')
宏任务与微任务
- 宏任务:setTimeout、setInterval、Ajax、DOM事件
- 微任务:Promise、async/await
- 微任务执行时机比宏任务要早
Event Loop 和 DOM 渲染
- 每次 Call Stack 清空(即每次轮询结束)
- 都是 DOM 重新渲染的机会,DOM 结构如有改变则重新渲染
- 然后再去触发下一次 Event Loop
图片出处:https://coding.imooc.com/lesson/400.html#mid=35181
宏任务和微任务的区别
- 宏任务:DOM 渲染后触发,如 setTimeout
- 微任务:DOM 渲染前触发,如 Promise
宏任务和微任务的根本区别
- 在 micro task queue 里执行当前的微任务
- 微任务比宏任务的执行时机更早
- 微任务发生在 DOM 渲染之前
- 宏任务发生在 DOM 渲染之后
JS 异步面试题
什么是宏任务和微任务、两者的区别
- 宏任务:setTimeout,setInterval,Ajax,DOM 事件
- 微任务:Promise,async/await
- 微任务执行时机比宏任务要早
场景题 — async、await 语法
- async 修饰的函数返回的是 promise 实例
- a 没有 await 修饰,输出的是 promise 实例
- b 有 await 修饰,相当于 then ,输出的是 100
示例 1:
async function fn() {
return 100
}
(async function() {
const a = fn()
const b = await fn()
console.log(a)
console.log(b)
})()
示例 2:失败后面的代码都不会执行
(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')
})()
promise 和 setTimeout 的顺序问题
示例:
console.log(100)
setTimeout(() => {
console.log(200)
})
Promise.resolve().then(() => {
console.log(300)
})
console.log(400)
async/await 的顺序问题
- 主线程 => 微任务 => 宏任务
- 初始化 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 (function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
手写 Promise 源码
class MyPromise {
state = 'pending'
value = undefined
reason = undefined
resolveCallbacks = []
rejectCallbacks = []
constructor(fn) {
const resolveHandler = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.resolveCallbacks.forEach(fn => fn(this.value))
}
}
const rejectHandler = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.rejectCallbacks.forEach(fn => fn(this.reason))
}
}
try {
fn(resolveHandler, rejectHandler)
} catch (err) {
rejectHandler(err)
}
}
then(fn1, fn2) {
fn1 = typeof fn1 === 'function' ? fn1 : (v) => v
fn2 = typeof fn2 === 'function' ? fn2 : (e) => e
if (this.state === 'pending') {
const p1 = new MyPromise((resolve, reject) => {
this.resolveCallbacks.push(() => {
try {
const newValue = fn1(this.value)
resolve(newValue)
} catch (err) {
reject(err)
}
})
this.rejectCallbacks.push(() => {
try {
const newReason = fn2(this.reason)
reject(newReason)
} catch (err) {
reject(err)
}
})
})
return p1
}
if (this.state === 'fulfilled') {
const p1 = new MyPromise((resolve, reject) => {
try {
const newValue = fn1(this.value)
resolve(newValue)
} catch (err) {
reject(err)
}
})
return p1
}
if (this.state === 'rejected') {
const p1 = new MyPromise((resolve, reject) => {
try {
const newReason = fn2(this.reason)
reject(newReason)
} catch (err) {
reject(err)
}
})
return p1
}
}
catch (fn) {
return this.then(null, fn)
}
}
MyPromise.resolve = function (value) {
return new MyPromise((resolve, reject) => resolve(value))
}
MyPromise.reject = function (reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
MyPromise.all = function (promiseList = []) {
const p1 = new MyPromise((resolve, reject) => {
const result = []
const length = promiseList.length
let resolvedCount = 0
promiseList.forEach(p => {
p.then(data => {
result.push(data)
resolvedCount++
if (resolvedCount === length) {
resolve(result)
}
}).catch(err => {
reject(err)
})
})
})
return p1
}
MyPromise.race = function (promiseList = []) {
let resolved = false
const p1 = new Promise((resolve, reject) => {
promiseList.forEach(p => {
p.then(data => {
if (!resolved) {
resolve(data)
resolved = true
}
}).catch((err) => {
reject(err)
})
})
})
return p1
}
不积跬步无以至千里 不积小流无以成江海
点个专注不迷路,持续更新中…
|