进程和线程
进程:cpu资源分配的最小单位,cpu一个进程工作时,其他进程停止
线程:cpu调度的最小单位,一个进程里有多个线程
CPU就好比是工厂,进程就是车间,线程就是工人,不过这个工厂很特殊,每次只有一个车间工作。
浏览器有很多进程:主进程,渲染进程,网络进程,GPU进程。
渲染进程里包含很多线程:GUI渲染线程,定时器触发线程,JS引擎线程,事件触发线程,异步http请求线程。
js是单线程
这是有因为多线程很复杂,js这个语言创建时多线程并不流行。
GUI渲染线程和js引擎线程互斥
js代码运行中可能会操作DOM造成页面更改,就可能会与渲染线程产生矛盾。
事件循环
事件分为同步任务和异步任务,异步任务又包括宏任务和微任务。
宏任务:script标签内容,setTimeout,setInterval,setImmediate 、I/O
微任务:promise,process.nextTick,MutationObserver
同步任务形成一个执行栈,事件触发线程管理一个任务队列,如果遇到异步任务,异步任务如果达到触发条件就会推入任务队列中等待执行,等执行栈中的同步任务执行完毕后开始执行任务队列中的任务。
事件循环执行顺序:同步任务>宏任务>微任务队列>渲染>宏任务>微任务队列>渲染>...循环
下面来做题吧(题目答案都是下拉形式方便大家做题)
1.基础promise
状态为pending时后续.then不执行
const promise = new Promise((resolve, reject) => { console.log(1); console.log(2); }); promise.then(() => { console.log(3); }); console.log(4);
结果
1 2 4
const promise = new Promise((resolve, reject) => { console.log(1); resolve("success"); console.log(2); }); promise.then(() => { console.log(3); }); console.log(4);
结果
1 2 4 3
这个promise状态为fulfilled 所以后续的.then会执行
2.结合setTimeout
遇到setTimeout代码按执行顺序搁置到达定时器时间后放入队列中。
Promise.resolve().then(() => { console.log("promise1"); const timer2 = setTimeout(() => { console.log("timer2"); }, 0); }); const timer1 = setTimeout(() => { console.log("timer1"); Promise.resolve().then(() => { console.log("promise2"); }); }, 0); console.log("start");
结果
start promise1 timer1 promise2 timer2
分析:遇到定时器可以将定时器这段代码放入队列中,如果有多个就需要按照执行顺序排列并且记录定时器时间。最后队列中的执行顺序会首先根据定时器时间然后根据执行顺序排列。
所以这段代码可以理解为
Promise.resolve().then(() => { console.log("promise1"); //2 }); //1 console.log("start"); 1. const timer1 = setTimeout(() => { console.log("timer1"); Promise.resolve().then(() => { console.log("promise2"); }); }, 0); 2. const timer2 = setTimeout(() => { console.log("timer2"); }, 0);
先执行:同步代码'start'>>微任务 'promise1'
主代码执行完毕后,将宏任务定时器代码执行,所以先执行1 "timer1" 定时器内部有一个微任务输出"promise2" 然后执行2 "timer2"
这里关于两个定时器的顺序:定时器2是微任务里的,所以自然放在后面。
如果第一个定时器的时间为1000,第二个定时器的时间为0,那么结果会不同。
Promise.resolve().then(() => { console.log("promise1"); const timer2 = setTimeout(() => { console.log("timer2"); }, 0); }); const timer1 = setTimeout(() => { console.log("timer1"); Promise.resolve().then(() => { console.log("promise2"); }); }, 1000);//修改的点 console.log("start");
结果
start promise1 timer2 timer1 promise2
执行顺序(重要)
console.log(1); new Promise((res) => { console.log(2); res(3); console.log(4); }) .then((res) => { console.log(res); return 10; }) .then((res) => { console.log(res); }); setTimeout(() => { console.log("kaishi"); }, 0); new Promise((res) => { console.log(6); res(7); console.log(8); }) .then((res) => { console.log(res); return 11; }) .then((res) => { console.log(res); });
结果
1 2 4 6 8 3 7 10 11 kaishi
分析:并列多个promise并且有多个.then会根据链的层次执行。定时器属于宏任务自然放在最后。
3.promise中的then catch finally
catch会捕获错误,本身返回一个promise
const promise = new Promise((resolve, reject) => { reject("error"); resolve("success2"); }); promise .then((res) => { console.log("then1: ", res); }) .then((res) => { console.log("then2: ", res); }) .catch((err) => { console.log("catch: ", err); }) .then((res) => { console.log("then3: ", res); });
结果
catch:error then3:undefined
分析:promise状态一经改变便不会修改,所以promise是rejected状态,这个时候catch两个.then都不会执行,直接执行catch代码,但是catch返回一个pengding状态的promise所以最后一个.then返回undefined。
如果catch前面的.then处理了错误,那么catch不会执行。
上面代码改造一下
const promise = new Promise((resolve, reject) => { reject("error"); resolve("success2"); }); promise .then( (res) => { console.log("then1: ", res); }, (rej) => { console.log("then1: ", rej); }//增加了reject ) .then((res) => { console.log("then2: ", res); }) .catch((err) => { console.log("catch: ", err); }) .then((res) => { console.log("then3: ", res); });
结果
then1: err
then2: undefined
then3: undefined
.then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获。
Promise.resolve() .then(() => { return new Error("error!!!"); }) .then((res) => { console.log("then: ", res); }) .catch((err) => { console.log("catch: ", err); });
结果
then: Error: error!!!
.then需要一个函数,不然会出现透传。
Promise.resolve(1) .then(2) .then(Promise.resolve(3)) .then(console.log);
结果
1
finally不接收参数(接受也是undefined)它最终返回的会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。
Promise.resolve("1") .then((res) => { console.log(res); }) .finally(() => { console.log("finally"); }); Promise.resolve("2") .finally(() => { console.log("finally2"); return "我是finally2返回的值"; //不起作用 }) .then((res) => { console.log("finally2后面的then函数", res); });
结果
1
finally2
finally
finally2后面的then函数 2
Promise.resolve("1") .finally(() => { console.log("finally1"); throw new Error("我是finally中抛出的异常"); }) .then((res) => { console.log("finally后面的then函数", res); }) .catch((err) => { console.log("捕获错误", err); });
结果
'finally1'
'捕获错误' Error: 我是finally中抛出的异常
4.async和await
核心:await返回一个promise 可以将await后面的代码看作promise.then链式
async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); // 这两行看作(只是类比) // new Promise(resolve => { // console.log("async2") // resolve() // }).then(res => console.log("async1 end")) } async function async2() { console.log("async2"); } async1(); console.log("start");
结果
1.async1 start
2.async2
3.start
4.async1 end
只要遇到await都可以进行这样看作,将嵌套的代码捋顺再将执行顺序搞懂会更好做题。
例如:
async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); setTimeout(() => { console.log("timer1"); }, 0); } async function async2() { setTimeout(() => { console.log("timer2"); }, 0); console.log("async2"); } async1(); setTimeout(() => { console.log("timer3"); }, 0); console.log("start"); 可以看作 console.log("async1 start"); new Promise((res) => { setTimeout(() => { console.log("timer2"); }, 0); console.log("async2"); res(); }).then((res) => { console.log("async1 end"); setTimeout(() => { console.log("timer1"); }, 0); }); setTimeout(() => { console.log("timer3"); }, 0); console.log("start");
结果
'async1 start'
'async2'
'start'
'async1 end'
'timer2'
'timer3'
'timer1'
如果把async2函数换成new Promise(resolve => {console.log('async2')})只改这一行
那么结果是: 1.async1 start 2.async2 3.async1 end 4.start
看出区别了吗,没有微任务了。所以改造后面代码是链式。
await后面跟penging状态的new promise
async function async1() { console.log("async1 start"); await new Promise((resolve) => { console.log("promise1"); }); console.log("async1 success"); return "async1 end"; } console.log("srcipt start"); async1().then((res) => console.log(res)); console.log("srcipt end");
结果
'script start' 'async1 start' 'promise1' 'script end'
将await后面的代码改造一下就看明白了,所以async最后两行不会执行,并且倒数第二行也不会执行。
await 后面跟fulfilled状态的new promise
async function async1() { console.log("async1 start"); await new Promise((resolve) => { console.log("promise1"); resolve("promise1 resolve"); }).then((res) => console.log(res)); console.log("async1 success"); return "async1 end"; } console.log("srcipt start"); async1().then((res) => console.log(res)); console.log("srcipt end");
结果
'script start' 'async1 start' 'promise1' 'script end' 'promise1 resolve' 'async1 success' 'async1 end'
看看综合题目
const async1 = async () => { console.log("async1"); setTimeout(() => { console.log("timer1"); }, 2000); await new Promise((resolve) => { console.log("promise1"); resolve(); //变式1 有无此行 }); console.log("async1 end"); return "async1 success"; }; console.log("script start"); async1().then((res) => console.log(res)); console.log("script end"); Promise.resolve(1) .then(2) .then(Promise.resolve(3)) .catch(4) //变式二 有无以上三行代码 .then((res) => console.log(res)); setTimeout(() => { console.log("timer2"); }, 1000);
结果
分析:
把整个代码顺序弄清除
1.两个定时器放在一边不管,看时间是否一致,这里不一致就很好判断谁先执行。
2.先输出”script start“
3.然后就进入async函数 输出”async1“
4.往下走跳过定时器,遇到await 执行后面promise输出”promise1“执行到resolve就出async函数,为什么?可以看我图片里的改造代码
5.输出”script end"
6.往下走遇到一个新的promise实例但是没有输出
这一轮走完,然后走微任务队列
7.返回到async函数中第一个微任务输出“async1 end”
根据多个并列promise实例按照链式层级执行,下面的promise第一个.then是透传,所以没有输出,
然后链式第二层,图中第六步async1().then((res) => console.log(res)); 输出async函数的返回值“async1 success”
下面的promise依然是透传所以没有输出
上面的promise执行完毕了,下面的promise继续执行,没有错误跳过catch,最后一个.then输出透传的值“1”
8.微任务队列执行完毕后进入下一个宏任务
执行1s的那个定时器输出“timer2”
9.没有对应的微任务队列,继续执行下一个宏任务
执行2s的定时器,输出“timer1”
大家可以试试我标注的两个变式,图中的无用代码其实并不是真正无用,打字失误.
总结
弄清楚事件循环整个过程以及哪些是宏任务和微任务。将复杂的代码捋顺很重要,多做题就会明白。