(八)JS异步进阶,更深更广搞定JS异步【想要进大厂,更多异步的问题等着你】

  • 之前讲解JS异步,在于初阶的应用
  • 本章在于JS异步的原理和进阶

提问

  • 请描述event loop(事件循环/事件轮询)的机制,可画图
  • 什么是宏任务和微任务,两者有什么区别
  • Promise有哪三种状态?如何变化
  • promise then和catch的连接
// 第一题
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
  • async/await 语法问题
async function fn() {
    return 100
}
(async function () {
    const a = fn() // ??               // promise对象
    const b = await fn() // ??         // 100
})()
(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')
})() // 执行完毕,打印出那些内容?//start  a,100  b,200
  • Promise 和 setTimeout 顺序
console.log(100)
setTimeout(() => {
    console.log(200)
})
Promise.resolve().then(() => {
    console.log(300)
})
console.log(400)
// 100 400 300 200
  • async/await的执行顺序问题
async function async1 () {
  console.log('async1 start') //2
  await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行
  console.log('async1 end') //6 await 后面的都作为回调内容---微任务
}

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. async1 函数中 await 后面的内容 —— 微任务
// 2. setTimeout —— 宏任务
// 3. then —— 微任务



// 同步代码执行完毕 (event loop - call stack 被清空)
// 执行微任务
// (尝试触发DOM渲染)
// 触发Event Loop,执行宏任务 

event loop

  • JS是单线程运行的
  • 异步要基于回调来实现
  • event loop就是异步回调的实现原理

JS如何执行

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

示例

console.log('Hi')

setTimeout(function cb1() {
    console.log('cb1') // cb 即 callback
}, 5000)

console.log('Bye')

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结event loop的过程

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

DOM事件和event loop

  • JS是单线程的
  • 异步(setTimeOut,ajax等)使用回调,基于event loop
  • Dom事件不是异步,但DOM事件也使用回调,基于event loop
//DOM 事件,也用 event loop
<button id="btn1">提交</button>

<script>
console.log('Hi')

$('#btn1').click(function (e) {
    console.log('button clicked')
})

console.log('Bye')
</script>

在这里插入图片描述

promise进阶

三种状态

  • pending resolved rejected
  • pending 》resolved 或 pending 》rejected
  • 变化不可逆

// 刚定义时,状态默认为 pending
const p1 = new Promise((resolve, reject) => {
})
console.log('p1',p1) // pending


// 执行 resolve() 后,状态变成 resolved
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve()
    })
})
console.log('p2',p2) // pending - 开始打印时
setTimeout(() => console.log('p2-setTimeout',p2)) //resolved


// 执行 reject() 后,状态变成 rejected
const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject()
    })
})
console.log('p3',p3) // pending - 开始打印时
setTimeout(() => console.log('p3-setTimeout',p3)) //rejected
 

在这里插入图片描述

状态的表现和变化

  • pending状态,不会触发then和catch
  • resolved状态,会触发后续的then回调函数
  • rejected状态,会触发后续的catch回调函数
const p1 = Promise.resolve(100)
console.log('p1',p1)
const p2 = Promise.reject('err')
console.log('p2',p2)

在这里插入图片描述

const p1 = Promise.resolve(100) //resolved
//console.log('p1',p1)
p1.then(data => {
	console.log('data',data)
}).catch(err => {
	console.error('err',err)
})
const p2 = Promise.reject('err') //rejected
//console.log('p2',p2)
p2.then(data => {
	console.log('data2',data)
}).catch(err => {
	console.error('err2',err)
})

在这里插入图片描述

then和catch对状态的影响(then和catch改变状态)

then catch 会继续返回 Promise ,此时可能会发生状态变化!!!

  • then正常返回resolved,里面有报错则返回rejected
  • catch正常返回resolved,里面有报错则返回rejected
const p1 = Promise.resolve().then(() => {
    return 100
})
console.log('p1',p1) //resolved
const p2 = Promise.resolve().then(() => {
    throw new Error('then error')
})
console.log('p2',p2) //rejected

在这里插入图片描述

const p1 = Promise.resolve().then(() => {
    return 100
})
console.log('p1',p1) //resolved 触发后续then回调
p1.then(() => {
	console.log('123')
})
const p2 = Promise.resolve().then(() => {
    throw new Error('then error')
})
console.log('p2',p2) //rejected 触发后续catch回调
p2.then(() => {
	console.log('456')
}).catch(err => {
	console.error('err100',err)
})
 

在这里插入图片描述

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.error('some err')
})

在这里插入图片描述

// then() 一般正常返回 resolved 状态的 promise
Promise.resolve().then(() => {
    return 100
})

// then() 里抛出错误,会返回 rejected 状态的 promise
Promise.resolve().then(() => {
    throw new Error('err')
})

// catch() 不抛出错误,会返回 resolved 状态的 promise
Promise.reject().catch(() => {
    console.error('catch some error')
})

// catch() 抛出错误,会返回 rejected 状态的 promise
Promise.reject().catch(() => {
    console.error('catch some error')
    throw new Error('err')
})
// 第一题
Promise.resolve().then(() => {
    console.log(1) //1
}).catch(() => {
    console.log(2)
}).then(() => {
    console.log(3) //3
})

// 第二题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
    console.log(1) //1
    throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
    console.log(2) //2
}).then(() => {
    console.log(3) //3
})

// 第三题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
    console.log(1) //1
    throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
    console.log(2) //2
}).catch(() => {
    console.log(3)
})

Promise总结

  • 三种状态,状态的表现和变化
  • then和catch对状态的影响
  • then和catch的链式调用

async/await

  • 异步调用callback hell
  • Promise then catch链式调用,但也是基于回调函数
  • async/await是同步语法实现异步,彻底消灭回调函数
function loadImg(src) {
    const promise = new Promise((resolve, reject) => {
        const img = document.createElement('img')
        img.onload = () => {
            resolve(img)
        }
        img.onerror = () => {
            reject(new Error(`图片加载失败 ${src}`))
        }
        img.src = src
    })
    return promise
}
const src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
const src2 = 'https://avatars3.githubusercontent.com/u/9583120'
(async function () {
    // 注意:await 必须放在 async 函数中,否则会报错
    // 加载第一张图片
    const img1 = await loadImg(src1)
    console.log(img1)
    // 加载第二张图片
    const img2 = await loadImg(src2)
    console.log(img2)
})()

会报
在这里插入图片描述
因为src2后面没加分号,和后面的()连在一起被当做函数,类似图示alert也会弹出弹框,解决办法:(async前面加!号

const src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
const src2 = 'https://avatars3.githubusercontent.com/u/9583120'
!(async function () {
    // 注意:await 必须放在 async 函数中,否则会报错
    // 加载第一张图片
    const img1 = await loadImg(src1)
    console.log(img1)
    // 加载第二张图片
    const img2 = await loadImg(src2)
    console.log(img2)
})()
//用同步的方式,编写异步。
async function loadImg1() {
    const src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
    const img1 = await loadImg(src1)
    return img1
}

async function loadImg2() {
    const src2 = 'https://avatars3.githubusercontent.com/u/9583120'
    const img2 = await loadImg(src2)
    return img2
}

!(async function () {
    // 注意:await 必须放在 async 函数中,否则会报错
    try {
        // 加载第一张图片
        const img1 = await loadImg1() //await后面不仅可以加promise对象也可以加async函数
        console.log(img1)
        // 加载第二张图片
        const img2 = await loadImg2()
        console.log(img2)
    } catch (ex) {
        console.error(ex)
    }
})()

async-await和Promise有什么关系

  • async/await是消灭异步回调的终极武器
  • 但和Promise并不互斥
  • 反而,两者相辅相成
  • 执行async函数,返回的是Promise对象,如果函数内没返回 Promise ,则自动封装成Promise对象
async function fn1() {
    return new Promise(200)
}
const res1 = fn1()//执行async函数,返回的是一个Promise对象
res1.then(data => {console.log('data',data)}) //200

async function fn2() {
    return 100 // 相当于 Promise.resolve(100)
}
console.log( fn2() ) //Promise对象
  • await 相当于Promise的then,await 后面可以加promise对象、值、async函数的执行结果
// await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 resolved ,才获取结果并继续执行
// await 后续跟非 Promise 对象:会直接返回
!(async function () {
    const p1 = Promise.resolve(100)
    const data = await p1 // await 相当于Promise的then
    console.log('data',data) // 100
})()
!(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 p1 = new Promise(() => {})
    await p1
    console.log('p1') // 不会执行
})()
!(async function () {
    const p4 = Promise.reject('some err') //rejected状态
    const res = await p4 // await -> then
    console.log(res) // 不会执行
})()
  • try…catch可捕获异常,代替了Promise的catch
!(async function () {
    const p4 = Promise.reject('some err') //rejected状态
    try {
        const res = await p4
        console.log(res)
    } catch (ex) {
        console.error(ex) // try...catch 相当于 promise catch
    }
})()
//总结
async 封装 Promise
await 处理 Promise 成功
try...catch 处理 Promise 失败

async/await是语法糖,异步的本质还是回调函数

  • async/await是消灭异步回调的终极武器
  • JS是单线程,还得是有异步,还得是基于event loop
  • async/await只是一个语法糖,但这颗糖真香
//只要遇到了 await ,后面的代码都相当于放在 callback 里
async function async1 () {
  console.log('async1 start') // 2
  await async2() //undefined
  // await 的后面都可以看做是calllback里的内容,即异步
  // 类似,event loop,setTimeout(cb1)
  // setTimeout(function() { console.log('async1 end') })
  // Promise.resolve().then(() => { console.log('async1 end') } ) //微任务/宏任务
  console.log('async1 end') // 5 关键在这一步,它相当于放在 callback 中,最后执行
}

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

console.log('script start') //1
async1() //立马执行async1函数体
console.log('script end') //4
// 同步代码已经执行完(event loop)

 
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)

 

for…of

  • for…in(以及forEach for) 是常规的同步遍历
  • for…of常用于异步的遍历
// 定时算乘法
function multi(num) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(num * num)
        }, 1000)
    })
}

// // 使用 forEach ,是 1s 之后打印出所有结果,即 3 个值是一起被计算出来的
// function test1 () {
//     const nums = [1, 2, 3];
//     nums.forEach(async x => {
//         const res = await multi(x);
//         console.log(res);
//     })
// }
// test1();

// 使用 for...of ,可以让计算挨个串行执行
async function test2 () {
    const nums = [1, 2, 3];
    for (let x of nums) {
        // 在 for...of 循环体的内部,遇到 await 会挨个串行计算
        const res = await multi(x)
        console.log(res)
    }
}
test2()

微任务microTask和宏任务macroTask

  • 什么是宏任务,什么是微任务
  • event loop和DOM渲染
  • 微任务和宏任务的区别
console.log(100)
setTimeout(() => {
    console.log(200)
})
Promise.resolve().then(() => {
    console.log(300)
})
console.log(400)
// 100 400 300 200

宏任务和微任务

  • 宏任务:setTimeout,setInterval,Ajax,DOM事件
  • 微任务:Promise async/await
  • 微任务执行时机比宏任务要早(先记住)

event loop和DOM渲染

  • 再次回归一遍event loop的过程
    在这里插入图片描述
  • JS是单线程的,而且和DOM渲染共用一个线程
  • JS执行的时候,得留一些时机供DOM渲染
  • 回顾event loop过程(增加DOM渲染时机)
    在这里插入图片描述
1.每次Call Stack清空(即每次轮询结束),即同步任务执行完,或者说异步代码推到Call Stack执行结束
2.都是DOM重新渲染的机会,DOM结构如有改变则重新渲染,(不一定非得渲染,就是给一次 DOM 渲染的机会!!!)
3.然后再去触发下一次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 渲染,便于查看效果)
// 到此,即本次 call stack 结束后(同步任务都执行完了),浏览器会自动触发渲染,不用代码干预

// 微任务:DOM渲染前触发
Promise.resolve().then(() => {
 console.log('length1',$('#container').children().length) //3
alert('Promise then') //DOM渲染了吗?NO
})
// 宏任务:DOM渲染后触发
setTimeout(() => {
 console.log('length2',$('#container').children().length) //3
alert('setTimeout') //DOM渲染了吗?yes
})

宏任务和微任务的区别

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

从event loop解释,为何微任务执行更早

  • 宏任务(浏览器规定的)
    在这里插入图片描述
    在这里插入图片描述
  • 微任务(ES6语法规定的)
    在这里插入图片描述
    在这里插入图片描述
微任务:ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何关于,即可一次性处理完,更快更及时。
宏任务:ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值