本文知识点:
- event loop
- promise 进阶
- async/await
- 微任务/宏任务
- 场景题-promise then和catch的连接
- 场景题-async/await语法
- 场景题- promise和setTimeout的顺序
- 场景题-外加async/await的顺序问题
问答题
1 请描述event loop(事件循环/事件轮询)的机制,可画图
2 什么是宏任务和微任务,两者有什么区别?
3 Promise有哪三种状态?如何变化?
- 请描述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('error1')
}).catch(()=>{
console.log(2);
}).then(()=>{
console.log(3);
})
//打印 1 2 3
//第三题
Promise.resolve().then(()=>{
console.log(1);
throw new Error('error1')
}).catch(()=>{
console.log(2);
}).catch(()=>{
console.log(3);
})
//打印 1 2
- 场景题-async/await语法
async function fn(){
return 100;
}
(async function(){
const a = fn();//?? 返回一个promise 值是100
const b = await fn(); //?? 返回值100(await 相当于promise的then)
})()
(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.resolve(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();
// await后面都作为回调内容 -- 微任务
console.log('async1 end') //6
}
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
//同步代码执行完毕(event loop - call stack 被清空)
//执行微任务
//(尝试触发DOM渲染)
//触发Event loop,执行宏任务
event loop(事件循环/事件轮询)
-
JS是单线程运行的
-
异步要基于回调来实现
-
event loop就是异步回调的实现原理
-
回顾:JS如何执行?
- 从前到后,一行一行执行
- 如果某一行执行报错,则停止下面代码的执行
- 先把同步代码执行完,再执行异步
示例
console.log('Hi')
setTimeout(function cb(){
console.log('cb1')
},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事件也使用回调,基于event loop
console.log('Hi')
setTimeout(function cb(){
console.log('cb1')
},5000)
console.log('Bye')
<button id="btn1">提交</button>
<script>
console.log('Hi')
$('#btn1').click(function(e){
console.log('button clicked')
})
console.log('Bye')
</script>
宏任务macroTask和微任务micro Task
- 什么是宏任务,什么是微任务
- 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渲染
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) //3
alert('本次 call stack结束,DOM结构已更新,但尚未触发渲染')
//(alert会阻断js执行,也会阻断DOM渲染,便于查看效果)
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)//3
alert('Promise then')//DOM 渲染了吗?---//NO
})
//宏任务:DOM渲染后触发
setTimeout(()=>{
console.log('length2',
$('#container').children().length)//3
alert('setTimeout') //DOM渲染了吗? ---YES
})
从event loop解释,为何微任务比宏任务更早,会在渲染前触发
- 宏任务是由浏览器规定的:setTimeout,setInterval,Ajax,DOM事件
- 微任务是ES6语法规定的:Promise async/await
微任务和宏任务-总结
- 宏任务有哪些?微任务有哪些?为什么微任务触发时机更早
- 微任务、宏任务和DOM渲染的关系
- 微任务、宏任务和DOM渲染,在event loop的过程
- 描述event loop机制(可画图)
-
自行回顾DOM渲染的关系
-
微任务和宏任务在event loop过程中的不同处理
-
Promise
- 三种状态
- 状态的表现和变化
- then 和 catch对状态的影响
- 可回顾 7-异步基础
//图片加载
function loadImg(src){
return 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
})
}
const url = 'https://img.xxx';
loadImg(url).then(img =>{
console.log(img.width);
return img;
}).then(img=>{
console.log(img.height)
}).catch(err=>console.error(err))
//解决回调地狱的问题:
const url1 = 'https://img1.xxx';
const url2 = 'https://img2.xxx';
loadImg(url1).then(img1 =>{
console.log(img.width);
return img1;
}).then(img1=>{
console.log(img1.height)
return loadImg(url2);
}).then(img2=>{
console.log(img2.width)
return img2;
}).then(img2=>{
console.log(img2.height)
}).catch(err=>console.error(err))
三种状态
-
pending(阻塞) resolved(成功) rejected(失败)
-
pending --> resolved 或 pending --> rejected
-
变化不可逆
-
状态的表现
- pending状态,不会触发then和catch
- resolved状态,会触发后续的then回调函数
- rejected状态,会触发后续的catch回调函数
const p1 = new Promise((resolve,reject)=>{
})
console.log('p1',p1) //pending
const p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve()
})
})
console.log('p2',p2)//pending
setTimeout(()=>console.log('p2-setTimeout',p2))//resolved
const p3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve()
})
})
console.log('p3',p3)//pending
setTimeout(()=>console.log('p3-setTimeout',p3))//reject
then和catch改变状态
- then正常返回resolve,里面有报错则返回rejected
- catch正常返回resolve,里面有报错则返回rejected
const p1 = Promise.resolve().then(()=>{
return 100
})
console.log('p1',p1) //resolved触发后续then回调
p1.then(()=>{
console.log('124')
})
const p2 = Promise.resolve().then(()=>{
throw new Error('then error')
})
console.log('p2',p2) //rejected触发后续catch回调
p2.then(()=>{
console.log('456')
}).catch(err=>{
console.log('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.log('some err')
})
打印结果如下:
async/await
- 异步回调callback hell
- Promise then catch链式调用,但也是基于回调函数
- async/await是同步语法,彻底消灭回调函数
//图片加载
function loadImg(src){
return 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
})
}
const url = 'https://img.xxx';
loadImg(url).then(img =>{
console.log(img.width);
return img;
}).then(img=>{
console.log(img.height)
}).catch(err=>console.error(err))
//解决回调地狱的问题:
const url1 = 'https://img1.xxx';
const url2 = 'https://img2.xxx';
定义一个匿名函数,await要用async包裹执行
(async function(){
const img1 = await loadImg(url1)
console.log(img1.width)
const img2 = await loadImg(url2)
console.log(img2.width)
})()
//相当于下面的函数
loadImg(url1).then(img1 =>{
console.log(img.width);
return img2;
}).then(img2=>{
console.log(img2.width)
})
定义一个匿名函数,await要用async包裹执行
(async function(){
const img1 = await loadImg(url1)
console.log(img1.width)
const img2 = await loadImg(url2)
console.log(img2.width)
})()
上面写法可以用下面的写法,意在举例await里面可以调用新的async await函数
async function loadImg1(url1){
const img1 = await loadImg(url1)
return img1
}
async function loadImg2(url2){
const img2 = await loadImg(url2)
return img2
}
(async function(){
const img1 = await loadImg1(url1)
console.log(img1.width)
const img2 = await loadImg2(url2)
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 两句意义相同
return Promise.resolve(100)
}
const res1 = fn1() //执行async函数,返回的是一个Promise对象
console.log('res1',res1) //Promise对象
res1.then(data=>{
console.log('data',data) //100
})
await相当于Promise.then
!(async function(){//!只是隔绝前面的语句,避免上一句没有';'等出错
const p1 = Promise.resolve(300)
const data = await p1 //相当于Promise.then
console.log('data',data)
})()
!(async function(){
const data1 = 222 //相当于Promise.resolve(222)
console.log('data1',data1)
})()
async function fn1(){
//return 100;//return 两句意义相同
return Promise.resolve(100)
}
!(async function(){
const data2 = await fn1()
console.log('data2',data2)
})()
使用try...catch
!(async function(){
const p4 = Promise.reject('err1')
try{
console.log('intry 1')
const res = await p4
console.log(res)
}catch(err){
console.log('p4err',err)// try...catch 相当于promise catch
}
})()
!(async function(){
const p5 = Promise.resolve('hello')
try{
console.log('intry 2')
const res = await p5
console.log(res)
}catch(err){
console.log('p5err',err)// try...catch 相当于promise catch
}
})()
打印:
intry 1
intry 2
p4err err1
hello
- 为什么要async await应该用try..catch?
- 因为await相当于Promise的then,处理不了catch
- try..cat可捕获异常,代替了Promise的catch
如下代码
!(async function(){
const p4 = Promise.reject('err1') //reject 状态
const res = await p4 //await -> then
console.log('res',res)
})()
//此代码无法捕获异常
异步的本质
- async/await 是消灭异步回调的终极武器
- JS还是单线程,还得是有异步,还得是基于event loop
- async/await 只是一个语法糖,但这颗糖真香!
async function async1(){
console.log('async1 start') //2
await async2()
//await的后面,都可以看做是callback里的内容,即异步
console.log('async1 end')//5
}
async function async2(){
console.log('async2')//3
}
console.log('script start') //1
async1()
console.log('script end')//4
//同步代码完则执行event loop
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)
})
//for...in同步的循环, 1秒后打印所有结果
!(async function(){
for( let i of nums){
const res = await muti(i);
console.log(res)
}
})()
//for...of异步的循环, 每一秒打印一个结果