JS异步进阶

  • 提问
  • event loop
    • JS如何执行
    • 示例
    • 总结event loop的过程
    • DOM事件和event loop
  • promise进阶
    • 三种状态
    • 状态的表现和变化
    • then和catch对状态的影响(then和catch改变状态)
    • Promise总结
  • async/await
    • async-await和Promise有什么关系
    • async/await是语法糖,异步的本质还是回调函数
  • for...of
  • 微任务microTask和宏任务macroTask
    • 宏任务和微任务
    • event loop和DOM渲染
    • 宏任务和微任务的区别
    • 从event loop解释,为何微任务执行更早
  • 之前讲解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(() => { // 注意这里是 catchconsole.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 100console.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') //2await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行console.log('async1 end') //6 await 后面的都作为回调内容---微任务
}async function async2 () {console.log('async2') //3
}console.log('script start') //1setTimeout(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 状态的 promiseconsole.log(1) //1throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promiseconsole.log(2) //2
}).then(() => {console.log(3) //3
})// 第三题
Promise.resolve().then(() => { // 返回 rejected 状态的 promiseconsole.log(1) //1throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promiseconsole.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)}) //200async 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的thenconsole.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 p1console.log('p1') // 不会执行
})()
!(async function () {const p4 = Promise.reject('some err') //rejected状态const res = await p4 // await -> thenconsole.log(res) // 不会执行
})()
  • try…catch可捕获异常,代替了Promise的catch
!(async function () {const p4 = Promise.reject('some err') //rejected状态try {const res = await p4console.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') // 2await 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') // 2await async2()// 下面三行都是异步回调 callback的内容console.log('async1 end') // 5await async3()console.log('async1 end 2')} //7async 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)干预处理。

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

  1. 你真的了解爬虫吗?看完你会对网络爬虫有更深更全面的认识

    前言 爬虫是门很有意思的技术,可以通过爬虫技术获取一些别人拿不到或者需要付费才能拿到的东西,也可以对大量数据进行自动爬取和保存,减少时间和精力去手动做一些累活. 可以说很多人学编程,不玩点爬虫确实少了 ...

  2. 更深更宽的孪生网络,有效提升目标跟踪精度,代码开源

    点击我爱计算机视觉标星,更快获取CVML新技术 本文为52CV群友Mr.Chen投稿,深入解读了CVPR 2019 跟踪方向的论文Deeper and Wider Siamese Networks f ...

  3. 2021年中国科技的脚步依旧不停 我们将走得更广更深更远

    央视网消息:2020年,我国在科技探索领域真的是很拼. 我们的征途是星辰大海,2021年中国科技的脚步依旧不停.截至今天(1月3日)6时,我国首次火星探测任务"天问一号"火星探测器 ...

  4. 腾讯AI Lab开放文本理解系统TexSmart,让AI想得更深更广

    感谢阅读腾讯AI Lab微信号第93篇文章.本文将介绍我们最新开放的自然语言理解系统TexSmart. 腾讯AI Lab今天宣布开放自然语言理解系统TexSmart,用以对中文和英文两种语言的文本进行 ...

  5. 向更深更远处迈进 | 数据计算解密神秘宇宙

    从1970年第一颗人造卫星"东方红一号"发射成功,到"神舟"飞天."嫦娥"奔月."天问"探火."北斗" ...

  6. 一文搞定JS事件基础与进阶

    1.事件简介 2.事件的调用方式         在script标签中使用         在元素中调用 3.鼠标事件         鼠标点击         鼠标移入移除         鼠标按住 ...

  7. 一文搞定JS中的DOM基础与进阶

    文章目录: 1.DOM简介 2.获取元素         getElementByld()         getElementsByTagName()         getElementsByCl ...

  8. 【一篇搞定JS数组】JavaScript数组使用详解

    文章目录 JavaScript 数组 Array 1)概述/前言 2)创建数组 (一)Array()构造函数 (二)数组字面量表示法 (三)扩展操作符 (四)工厂方法--Array.of()和Arra ...

  9. selenium 反爬虫之跳过淘宝滑块验证!首先要搞定JS!

    在处理问题的之前,给大家个第一个锦囊! 你需要将chorme更新到最新版版本84,下载对应的chorme驱动 驱动地址 注意 划重点!!一定要做这一步,因为我用的83的chorme他是不行滴,~~~~ ...

最新文章

  1. UIPageControl
  2. Android BOOTCLASSPATH详解
  3. python策略模式的应用_Head First 设计模式——策略模式(Strategy Pattern)——Python实现 | 学步园...
  4. POJ 3104 Drying [二分 有坑点 好题]
  5. 【ZOJ - 4024】Peak(模拟,水题)
  6. 切洋葱怎么才不流泪?
  7. flowable DMN部署单独使用_06
  8. 不要为了“分库分表”而“分库分表”
  9. 计算机基础(三):srpintf()函数小结
  10. 羊毛之家,青龙脚本,几乎全部可用
  11. QT5+VC2012配置以及常见错误
  12. 测试键盘是否灵敏的软件,u盘启动大师pe检测键盘灵敏度图文教程
  13. 关于header file、static、inline、variable hides的一点感想
  14. Hbase统计表的行数的3种方法
  15. 难怪好人有恶报,原来秘密在这里
  16. 一图看懂FC存储网络架构
  17. C++day01 const和define之间的区别
  18. Android 消息通知
  19. HTML5笔记+案例
  20. amd一键超频怎么用_AMD R5-1400处理器超频如何设置【设置教程】

热门文章

  1. python利用cookie模拟登录
  2. 将String转化为Long,并将Long转化为Date
  3. Akka并发编程——第六节:Actor模型(五)
  4. 向量时钟Vector Clock in Riak
  5. spark mllib源码分析之随机森林(Random Forest)
  6. 3012C语言_数据
  7. 翻版“永恒之蓝”入侵手机 信息安全再临大考
  8. 雅虎欲出售核心业务 忠诚用户何去何从
  9. idea如何彻底删除一个项目
  10. Java 匿名内部类理解