event loop(事件循环/事件轮询)
JS是单线程运行的
异步是基于回调来实现的
event loop就是异步回调的基本原理
event loop执行过程,简单图示
过程简述:1、当调用栈执行第一行代码后,在浏览器输出结果,并清空调用栈;
2、调用栈执行setTimeout函数(异步),在Web APIs中生成一个定时任务,等待触发回调,然后清空调用栈,此步骤无输出结果;
3、调用栈执行最后一行,在浏览器输出结果,清空调用栈;
4、前三步会在ms单位执行完毕,此时调用栈为空,启动event loop,它会往回调队列读取触发的回调函数,若为空,则继续轮询等待;当定时任务等异步事件被触发,就会被放到回调队列中,经event loop送到调用栈中执行,此时cb1函数被执行,在浏览器打印结果。
总结event loop执行过程
- 同步代码,一行行放在Call Stack中执行
- 遇到异步,会先“记录”下,等待时机(网络请求、定时等)
- 时机到了,就移动到Callback Queue
- 若Call Stack为空(同步代码执行完),Event loop则开始工作
- 轮询查找Callback Queue,若有则移动到Call Stack中执行
- 然后继续轮询查找(像永动机)
DOM和 Event loop
<button id="bn1">提交</button>
<script>
console.log('Hi');
$('#bn1').click( () =>{
console.log('button click');
})
console.log('Bye');
</script>
DOM事件(不是异步)也使用回调函数,原理基于回调,也基于Event loop。触发时机是用户的交互,Call stack执行click这行代码,将箭头函数存在Web APIs中,等待用户触发,再移动到Callback Queue执行下面的操作
Promise三种状态
- pending、resolved(或者叫fulfilled)、rejected
- pending -> resolved 或 pending -> rejected
- 变化不可逆
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()后 resolved
setTimeout(() => {console.log('p2-setTimeout',p2);}) //resolved
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject()
})
})
console.log('p3',p3);//pending 一开始打印时;执行setTimeout()后 rejected
状态的表现
- pending状态,不会触发then 或 catch
- resolved状态(也称fulfilled),会触发后续的then
- rejected状态,会触发后续的catch
<script>
const p1 = Promise.resolve(100);
console.log('p1',p1); //resolved或者显示fulfilled
p1.then(data => {
console.log('data',data); //100
}).catch(err => { //不会执行catch
console.log(err);
})
const p2 = Promise.reject('error');
console.log('p2',p2);
p2.then(data => { //rejected状态,直接执行catch
console.log('data',data);
}).catch(err => {
console.log(err); //'error'
})
</script>
then 和 catch改变状态
- then正常返回resolved,里面有报错则返回rejected
- catch正常返回resolved,里面有报错则返回rejected
<script>
const p1 = Promise.resolve().then(()=> {
return 100;
})
console.log('p1',p1); //resolved或者显示fulfilled,触发then
const p2 = Promise.resolve().then(()=> {
throw new Error('p2-error')
})
console.log('p2',p2); //rejected,可以触发catch
p2.then(()=>{}).catch(()=>{}) //此时触发catch
const p3 = Promise.reject().catch(()=> {
return 300;
})
console.log('p3',p3); //resolved
const p4 = Promise.resolve().then(()=> {
throw new Error('p4-error')
})
console.log('p4',p4); //rejected
</script>
-
关于then和catch面试
Promise.resolve().then(() =>{
console.log(1);
}).catch(()=> {
console.log(2);
}).then(()=> {
console.log(3);
})
//1,3 resolve()触发then(),第一个then没有抛出错误,正常返回resolved,触发第二个then,结 果输出1,3
Promise.resolve().then(()=> {
console.log(1);
throw new Error('error')
}).catch(() => {
console.log(2);
}).then(()=> {
console.log(3);
})
//输出1,2,3 resolved状态触发第一个then,打印1后抛出错误,返回状态rejected,触发catch,打印2未抛出新错误,返回状态resolved,触发下一个then
Promise.resolve().then(() => {
console.log(1);
throw new Error('error'); //rejected
}).catch(()=> {
console.log(2); //resolved,不再触发下一个catch
}).catch(()=> {
console.log(3);
})
async-await
async-await是用同步的写法而非回调的写法写异步;
async/await和Promise并不互斥,是相辅相成的
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
})
return p
}
const src1 = 'https://www.imooc.com/static/img/index/logo2020.png'
//Promise的写法
// loadImg(src1).then(img =>{
// console.log(img.width);
// return img
// }).then(img => {
// console.log(img.height);
// }).catch(ex => { console.error(ex) })
//async/await
//注意,! 可以帮助把前后文隔开,在前文未写分号的情况下,不会把二者认成一个函数
!(async function() {
//同步的写法
const img1 = await loadImg(src1);
console.log(img1.width, img1.height);
})()
async-await和 Promise的关系
- 执行async函数,返回的是Promise对象
- await相当于Promise的then
- 可用try...catch的方式捕获异常,代替了Promise的catch(其他语言都是用try...catch,更合理)
执行async函数,返回的是Promise对象
async function fn1() {
//return 100; //返回的是一个Promise对象,相当于 return Promise.resolve(100)
return Promise.resolve(200);
}
const res1 = fn1() //执行async函数,返回的是一个Promise对象
//console.log('res1', res1); //Promise对象
res1.then(data => {
console.log('data', data);
})
await相当于Promise的then
!(async function() {
const p1 = Promise.resolve(3000)
const data = await p1; //(1)跟Promise对象,await 相当于 Promise.then
console.log('data',data);
})()
!(async function() {
const data1 = await 300; //(2)跟一个值,300被封装成Promise对象
console.log('data1',data1);
})()
async function fn() {
return 100; //返回的是一个Promise对象,相当于 return Promise.resolve(100)
}
!(async function() {
const data2 = await fn(); //(3) 跟一个async函数
console.log('data2',data2); //100
})()
可用try...catch的方式捕获异常,代替了Promise的catch
!(async function() {
const p4 = Promise.reject('err')
try {
const res = await p4
console.log(res); //前一句抛出错误,这一句不会打印
} catch(ex) {
console.error(ex); //try...catch 相当于 Promise的catch
}
})()
!(async function() {
const p5 = Promise.reject('err1')
const res = await p5 //await相当于Promise的 then,rejected状态下不会被执行
console.log(res);
})
总结:异步的本质
- async/await 是消灭回调的终极武器
- JS是单线程,还是得有异步,还是得基于 event loop
- async/ await 是一个语法糖,从语法层面用类似同步的写法解决异步回调(callback hell),但消灭不了异步,因为上一点始终成立;(await 后面都可以看做异步回调 callback的内容)