event loop(事件循环/事件轮询)
- JS是单线程运行的
- 异步要基于回调来实现
- event loop 就是异步回调的实现机制
JS执行过程:从前到后,一行一行执行;如果某一行执行报错,则停止下面代码的执行;先把同步代码执行完;再执行异步。
event loop的执行过程(极其重要!)


(异步)示例如下:
- 将console.log(“Hi”)推入调用栈,调用栈会执行代码执行代码,控制台打印“Hi”,调用栈清空
- 执行setTimeout,setTimeout由浏览器定义,不是ES6的内容;将定时器到Web APIs中,到时间后将回调函数放到回调函数队列中
- 执行完了setTimeout,清空调用栈
- console.log(“Bye”)进入调用栈,执行,调用栈清空
- 同步代码被执行完,,回调栈空,浏览器内核启动时间循环机制五秒之后,定时器将cb1推到回调函数队列中
- 事件循环将cb1放入调用栈
(DOM事件)示例如下:
- 将console.log(“Hi”)推入调用栈,调用栈会执行代码执行代码,控制台打印“Hi”,调用栈清空
- 执行click,click由浏览器定义,不是ES6的内容;将click到Web APIs中,当有用户点击时将回调函数放到回调函数队列中
- 执行完了click,清空调用栈
- console.log(“Bye”)进入调用栈,执行,调用栈清空
- 同步代码被执行完,,回调栈空,浏览器内核启动时间循环机制,click将回调函数推到回调函数队列中
- 事件循环将回调函数放入调用栈
- 打印“button clicked”,调用栈清空
DOM事件和event loop
- pending :在过程中
- resolved :解决了
- rejected:失败了
状态的表现和变化
- pending状态:不会触发then或catch
- resolved状态:会触发后续的then回调函数
- rejected状态:会触发后续的catch回调函数
then 和 catch对状态的影响(重要!)
- then正常返回resolved,里面有报错则返回rejected(then其实也是一个新的promise,状态为pending)
- catch正常返回resolved,里面有报错则返回rejected(catch其实也是一个新的promise,状态为pending)
promise关于then和catch的面试题
Promise.resolve().then(() => {console.log(1) //1}).catch(() => {console.log(2)}).then(() => {console.log(3)//3})
- 先打印“1”
- 因为.then里如果不报错,就会返回一个resolve状态的promise
所以执行.then,打印“3”
Promise.resolve().then(() => {console.log(1)throw new Error('erro1')}).catch(() => {console.log(2)}).then(() => {console.log(3)})
1
- 2
3 因为catch不报错会返回一个resolve状态的promise
Promise.resolve().then(() => {console.log(1)throw new Error('erro1')}).catch(() => {console.log(2)}).catch(() => { // 注意这里是 catchconsole.log(3)})
1
- 2
async/await语法介绍
为了解决异步回调地狱,我们引入了Promise then catch链式调用,但也是基于回调函数的。async/await是同步语法,可以彻底消灭回调函数。
示例如下,将promise加载图片使用async/await进行改进,用同步的方式编写异步 ```javascript function loadImg(src) { const promise = new Promise((resolve, reject) => {
}) return promise }const img = document.createElement('img')img.onload = () => {resolve(img)}img.onerror = () => {reject(new Error(`图片加载失败 ${src}`))}img.src = src
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() console.log(img1) // 加载第二张图片 const img2 = await loadImg2() console.log(img2) } catch (ex) { console.error(ex) } })()
<a name="Z9RtL"></a>## async/await和Promise的关系- async/await是消火异步回调的终极武器- 但和Promise并不互斥- 反而,两者相辅相成**总结来看,async 封装 Promise,await 处理 Promise 成功(相当于then),try...catch 处理 Promise 失败。**_**(极其重要!!!)**_<a name="vJ97p"></a>### 执行async 函数,返回的是Promise 对象```javascriptasync function fn2() {return new Promise(() => {})//返回promise对象}console.log( fn2() )async function fn1() {return 100 //返回值}console.log( fn1() ) // 相当于 Promise.resolve(100)
执行async会返回一个promise对象。如果返回的是个值会封装成promise返回。如果是promise对象,那么就返回此对象
await相当于Promise的then
- await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 resolved ,才获取结果并继续执行 ```javascript (async function () { const p1 = new Promise(() => {}) await p1 console.log(‘p1’) // 不会执行 })()
(async function () { const p2 = Promise.resolve(100) const res = await p2 console.log(res) // 100 如果后面跟的是promise,await当做then使用 })()
(async function () {
const res = await 100
console.log(res) // 100 如果后面跟的是一个数,直接返回这个数
})()
(async function () { const p3 = Promise.reject(‘some err’) const res = await p3 console.log(res) // 不会执行 })()
<a name="ccDPP"></a>### try...catch 可捕获异常,代替了Promise的catch```javascript(async function () {const p4 = Promise.reject('some err')try {const res = await p4console.log(res)//此条语句会被执行} catch (ex) {console.error(ex)}})()
async/await是语法糖,异步的本质还是回调函数
async/await是消灭异步回调的终极武器
JS还是单线程,还得是有异步,还得是基于event loop
async/await只是一个语法糖,但这颗糖真香!

await 是同步写法,但本质还是异步调用。即,只要遇到了 await ,后面的代码都相当于放在 callback 里。
for…of
- for in 、 forEach、for是常规的同步遍历
- for of 常用于异步的遍历
同步遍历的forEach不会等待异步的返回结果,用async也不会等待。经过尝试for in和for是支持异步,forEach不支持。
// 定时算乘法function multi(num) {return new Promise((resolve) => {setTimeout(() => {resolve(num * num)}, 1000)})}// // 使用 forEach ,是 1s 之后打印出所有结果,即 3 个值是一起被计算出来的//因为它是一个同步遍历。同步遍历不会等待任何东西。它会一遍一遍执行。//一瞬间将遍历数字执行muti进行了三遍。然后1秒后一起打印出来。// 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()
什么是宏任务和微任务
- 宏任务: setTimeout ,setInterval,Ajax,DOM事件
- 微任务:Promise (对于前端来说),async/await
微任务比宏任务执行的更早
console.log(100)setTimeout(() => {console.log(200)})Promise.resolve().then(() => {console.log(300)})console.log(400)// 100 400 300 200
event loop和DOM渲染
JS是单线程的,而且和DOM渲染共用一个线程
- JS执行的时候,得留一些时机供DOM渲染
再次回顾 event loop 的过程:
- 每一次 call stack 结束(即第一次同步代码全部执行完),都会触发 DOM 渲染(不一定非得渲染,就是给一次 DOM 渲染的机会!!!)
- 然后再进行 event loop(cb1放进call Stack执行完之后Call Stack又空闲了,再次尝试DOM渲染)

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渲染后触发,如setTimeout
- 微任务:DOM渲染前触发,如Promise
```javascript
// 修改 DOM
const $p1 = $(‘
一段文字
‘) const $p2 = $(‘一段文字
‘) const $p3 = $(‘一段文字
‘) $(‘#container’) .append($p1) .append($p2) .append($p3)
// // 微任务:渲染之前执行(DOM 结构已更新)
Promise.resolve().then(() => {
console.log = $(‘#container’).children().length
alert(micro task ${length})//DOM渲染了吗?—-NO
})
// 宏任务:渲染之后执行(DOM 结构已更新)
setTimeout(() => {
console.log(length2,
$(#container).children().length)
alert(setTimeout)//DOM渲染了吗?—-yes
})
<a name="w4e7k"></a>## 微任务和宏任务的根本区别- 微任务:ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何干预,即可一次性处理完,更快更及时。- 宏任务:ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。微任务和宏任务在 event loop 会放在不同的地方等待<br /><br />则总体执行流程会发生变化,标准执行过程如下:<a name="xMsOJ"></a>## 面试题<a name="Mex3a"></a>### 描述 event loop机制(可画图)<a name="Ible3"></a>### 什么是宏任务和微任务,两者的区别是什么?<a name="tzPVn"></a>### promise的三种状态,如何变化<a name="XuIM3"></a>### 场景题-promise then和catch的连接<a name="t4LxN"></a>### 场景题-async/await语法<a name="I9VLP"></a>### 场景题-promise和setTimeout的顺序<a name="j9hYn"></a>### 场景题-外加async/await的顺序问题```javascriptasync function async1 () {console.log('async1 start')//2await async2()//await后面都作为回调内容-微任务console.log('async1 end') // 6async function async2 () {console.log('async2')//3}console.log('script start')//1setTimeout(function () { // 宏任务 setTimeoutconsole.log('setTimeout')//8}, 0)async1()//初始化promise时,传入的函数会立即执行new Promise (function (resolve) {console.log('promise1') // 4resolve()}).then (function () { // 微任务console.log('promise2')//7})console.log('script end')5// 同步代码执行完毕(event loop-call stack被清空)//执行微任务// 尝试触发DOM渲染// 触发 event loop,执行宏任务

