js的執(zhí)行順序,先同步后異步
異步中任務(wù)隊(duì)列的執(zhí)行順序: 先微任務(wù)microtask隊(duì)列,再宏任務(wù)macrotask隊(duì)列
調(diào)用Promise 中的resolve,reject屬于微任務(wù)隊(duì)列,setTimeout屬于宏任務(wù)隊(duì)列
注意以上都是 隊(duì)列,先進(jìn)先出。
微任務(wù)包括 process.nextTick ,promise ,MutationObserver。
宏任務(wù)包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering。
事件循環(huán)是什么
首先,JavaScript是一門單線程的語(yǔ)言,意味著同一時(shí)間內(nèi)只能做一件事,但是這并不意味著單線程就是阻塞,而實(shí)現(xiàn)單線程非阻塞的方法就是事件循環(huán)
在JavaScript中,所有的任務(wù)都可以分為
- 同步任務(wù):立即執(zhí)行的任務(wù),同步任務(wù)一般會(huì)直接進(jìn)入到主線程中執(zhí)行
- 異步任務(wù):異步執(zhí)行的任務(wù),比如ajax網(wǎng)絡(luò)請(qǐng)求,setTimeout定時(shí)函數(shù)等
同步任務(wù)與異步任務(wù)的運(yùn)行流程圖如下:

從上面我們可以看到,同步任務(wù)進(jìn)入主線程,即主執(zhí)行棧,異步任務(wù)進(jìn)入任務(wù)隊(duì)列,主線程內(nèi)的任務(wù)執(zhí)行完畢為空,會(huì)去任務(wù)隊(duì)列讀取對(duì)應(yīng)的任務(wù),推入主線程執(zhí)行。上述過(guò)程的不斷重復(fù)就事件循環(huán)
調(diào)用棧(Call Stack)
是一種后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)。當(dāng)一個(gè)腳本執(zhí)行的時(shí)候,js引擎會(huì)解析這段代碼,并將其中的同步代碼按照?qǐng)?zhí)行順序加入調(diào)用棧中,然后從頭開始執(zhí)行。
事件隊(duì)列 (Task Queue)
js引擎遇到一個(gè)異步任務(wù)后并不會(huì)一直等待其返回結(jié)果,而是會(huì)將這個(gè)任務(wù)交給瀏覽器的其他模塊進(jìn)行處理(以webkit為例,是webcore模塊),繼續(xù)執(zhí)行調(diào)用棧中的其他任務(wù)。當(dāng)一個(gè)異步任務(wù)返回結(jié)果后,js引擎會(huì)將這個(gè)任務(wù)加入與當(dāng)前調(diào)用棧不同的另一個(gè)隊(duì)列,我們稱之為事件隊(duì)列。
當(dāng)一個(gè)腳本執(zhí)行的時(shí)候,js引擎會(huì)解析這段代碼,并將其中的同步代碼按照?qǐng)?zhí)行順序加入調(diào)用棧中,然后從頭開始執(zhí)行。
js引擎遇到一個(gè)異步事件后并不會(huì)一直等待其返回結(jié)果,而是會(huì)將這個(gè)事件掛起(其他模塊進(jìn)行處理),繼續(xù)執(zhí)行執(zhí)行棧中的其他任務(wù)。當(dāng)一個(gè)異步事件返回結(jié)果后,js會(huì)將這個(gè)事件加入到事件隊(duì)列。
被放入事件隊(duì)列不會(huì)立刻執(zhí)行其回調(diào),而是等待當(dāng)前執(zhí)行棧中的所有任務(wù)都執(zhí)行完畢, 主線程處于閑置狀態(tài)時(shí),主線程會(huì)去查找事件隊(duì)列是否有任務(wù)。如果有,那么主線程會(huì)從中取出排在第一位的事件,并把這個(gè)事件對(duì)應(yīng)的回調(diào)放入執(zhí)行棧中,然后執(zhí)行其中的同步代碼…,如此反復(fù),這樣就形成了一個(gè)無(wú)限的循環(huán)。這個(gè)過(guò)程被稱為“事件循環(huán)(Event Loop)”。
同步任務(wù)
首先,我們用一個(gè)棧來(lái)表示主線程

當(dāng)有多個(gè)同步任務(wù)時(shí),這些同步任務(wù)會(huì)依次入棧出棧,如下圖

同步任務(wù)1先入棧,執(zhí)行完之后,出棧,接著同步任務(wù)2入棧,依此類推
這只是同步任務(wù)的執(zhí)行方式,那么異步任務(wù)呢?
異步任務(wù)
異步任務(wù)會(huì)在同步任務(wù)執(zhí)行之后再去執(zhí)行,那么如果異步任務(wù)代碼在同步任務(wù)代碼之前呢?在js機(jī)制里,存在一個(gè)隊(duì)列,叫做任務(wù)隊(duì)列,專門用來(lái)存放異步任務(wù)。也就是說(shuō),當(dāng)異步任務(wù)出現(xiàn)的時(shí)候,會(huì)先將異步任務(wù)存放在任務(wù)隊(duì)列中,當(dāng)執(zhí)行完所有的同步任務(wù)之后,再去調(diào)用任務(wù)隊(duì)列中的異步任務(wù)
例如下圖,現(xiàn)在存在兩個(gè)同步任務(wù),兩個(gè)異步任務(wù)

js會(huì)先將同步任務(wù)1提至主線程,然后發(fā)現(xiàn)異步任務(wù)1和2,則將異步任務(wù)1和2依次放入任務(wù)隊(duì)列

異步任務(wù)分類
js中,又將異步任務(wù)分為宏任務(wù)和微任務(wù),所以,上述任務(wù)隊(duì)列也分為宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列,那么,什么是宏任務(wù),什么是微任務(wù)呢?
I/O、定時(shí)器、事件綁定、ajax等都是宏任務(wù)
Promise的then、catch、finally和process的nextTick都是微任務(wù)
注意:Promise的then等方法是微任務(wù),而Promise中的代碼是同步任務(wù),并且,nextTick的執(zhí)行順序優(yōu)先于Promise的then等方法,因?yàn)閚extTick是直接告訴瀏覽器說(shuō)要盡快執(zhí)行,而不是放入隊(duì)列
js中,微任務(wù)總是先于宏任務(wù)執(zhí)行,也就是說(shuō),這三種任務(wù)的執(zhí)行順序是:同步任務(wù)>微任務(wù)>宏任務(wù)
宏任務(wù)與微任務(wù)
如果將任務(wù)劃分為同步任務(wù)和異步任務(wù)并不是那么的準(zhǔn)確,舉個(gè)例子:
console.log(1)
setTimeout(()=>{
console.log(2)}, 0)
new Promise((resolve, reject)=>{
console.log('new Promise')
resolve()
}).then(()=>{
console.log('then')
})
console.log(3)
如果按照上面流程圖來(lái)分析代碼,我們會(huì)得到下面的執(zhí)行步驟:
- console.log(1),同步任務(wù),主線程中執(zhí)行
- setTimeout() ,異步任務(wù),放到 Event Table,0 毫秒后console.log(2)回調(diào)推入 Event Queue 中
- new Promise ,同步任務(wù),主線程直接執(zhí)行
- .then ,異步任務(wù),放到 Event Table
- console.log(3),同步任務(wù),主線程執(zhí)行
所以按照分析,它的結(jié)果應(yīng)該是 1 => 'new Promise' => 3 => 2 => 'then'
但是實(shí)際結(jié)果是:1=>'new Promise'=> 3 => 'then' => 2
出現(xiàn)分歧的原因在于異步任務(wù)執(zhí)行順序,事件隊(duì)列其實(shí)是一個(gè)“先進(jìn)先出”的數(shù)據(jù)結(jié)構(gòu),排在前面的事件會(huì)優(yōu)先被主線程讀取
例子中 setTimeout回調(diào)事件是先進(jìn)入隊(duì)列中的,按理說(shuō)應(yīng)該先于 .then 中的執(zhí)行,但是結(jié)果卻偏偏相反
原因在于異步任務(wù)還可以細(xì)分為微任務(wù)與宏任務(wù)
微任務(wù)
一個(gè)需要異步執(zhí)行的函數(shù),執(zhí)行時(shí)機(jī)是在主函數(shù)執(zhí)行結(jié)束之后、當(dāng)前宏任務(wù)結(jié)束之前
常見的微任務(wù)有:
- Promise.then
- MutaionObserver
- Object.observe(已廢棄;Proxy 對(duì)象替代)
- process.nextTick(Node.js)
宏任務(wù)
宏任務(wù)的時(shí)間粒度比較大,執(zhí)行的時(shí)間間隔是不能精確控制的,對(duì)一些高實(shí)時(shí)性的需求就不太符合
常見的宏任務(wù)有:
- script (可以理解為外層同步代碼)
- setTimeout/setInterval
- UI rendering/UI事件
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
這時(shí)候,事件循環(huán),宏任務(wù),微任務(wù)的關(guān)系如圖所示

按照這個(gè)流程,它的執(zhí)行機(jī)制是:
- 執(zhí)行一個(gè)宏任務(wù),如果遇到微任務(wù)就將它放到微任務(wù)的事件隊(duì)列中
- 當(dāng)前宏任務(wù)執(zhí)行完成后,會(huì)查看微任務(wù)的事件隊(duì)列,然后將里面的所有微任務(wù)依次執(zhí)行完
回到上面的題目
console.log(1)
setTimeout(()=>{
console.log(2)}, 0)
new Promise((resolve, reject)=>{
console.log('new Promise')
resolve()
}).then(()=>{
console.log('then')
})
console.log(3)
流程如下
遇到 console.log(1) ,直接打印 1
遇到定時(shí)器,屬于新的宏任務(wù),留著后面執(zhí)行
遇到 new Promise,這個(gè)是直接執(zhí)行的,打印 'new Promise'
.then 屬于微任務(wù),放入微任務(wù)隊(duì)列,后面再執(zhí)行
遇到 console.log(3) 直接打印 3
好了本輪宏任務(wù)執(zhí)行完畢,現(xiàn)在去微任務(wù)列表查看是否有微任務(wù),發(fā)現(xiàn) .then 的回調(diào),執(zhí)行它,打印 'then'
當(dāng)一次宏任務(wù)執(zhí)行完,再去執(zhí)行新的宏任務(wù),這里就剩一個(gè)定時(shí)器的宏任務(wù)了,執(zhí)行它,打印 2
async與await
async 是異步的意思,await則可以理解為 async wait。所以可以理解async就是用來(lái)聲明一個(gè)異步方法,而 await是用來(lái)等待異步方法執(zhí)行
async和await是es7提供的語(yǔ)法,相比于es6的promise ,具有更高的代碼可讀性
從字面意思理解async是異步的意思,await是等待的意思,那么他們的作用就很容易看出了:
async : 聲明一個(gè)函數(shù)是異步的
await : 等待一個(gè)異步函數(shù)執(zhí)行完成
語(yǔ)法注意:await必須聲明在async內(nèi)部,因?yàn)閍sync會(huì)阻斷后邊代碼的執(zhí)行,說(shuō)到阻斷大家不要慌,因?yàn)檫@里的阻斷都是在一個(gè)async聲明的promise函數(shù)里的阻斷,不會(huì)影響整體代碼的執(zhí)行,async外邊的代碼還是照常執(zhí)行
async語(yǔ)法的優(yōu)勢(shì)
async語(yǔ)法在處理then鏈的時(shí)候有優(yōu)勢(shì),我們?nèi)绻胮romise的then嵌套可能會(huì)寫出多個(gè)then鏈,雖然解決了回調(diào)地獄的問題,但是感覺好亂啊
用async和await實(shí)現(xiàn)多接口調(diào)用的代碼:
function callServe1(){
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve({result:"callserve1"})
}, 1000);
})}
function callServe2(res){
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(res)
}, 1000);
})}
function callServe3(res){
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(res)
}, 1000);
})}
async function getAll(){
const result1 = await callServe1()
const result2 = await callServe2(result1)
const result3 = await callServe3(result2)
console.log(result3) //{ result: 'callserve1' }}
getAll()
async
1.函數(shù)前面加上 async 關(guān)鍵字,則該函數(shù)會(huì)返回一個(gè)結(jié)果為 promise 的對(duì)象。
2. async 函數(shù)返回 promise 對(duì)象的狀態(tài)。
2.1:如果返回的是一個(gè)非 Promise 類型的數(shù)據(jù), 則async 函數(shù)返回 promise 的狀態(tài) 為 fulfilled 成功。
2.2:如果返回的是一個(gè) Promise對(duì)象,則 async 函數(shù)返回 promise 的狀態(tài)由返回的Promise對(duì)象的狀態(tài)決定。
2.3:如果 throw Errow 拋出異常,則 async 函數(shù)返回 promise 的狀態(tài)為 rejected 失敗。
async函數(shù)返回一個(gè)promise對(duì)象,下面兩種方法是等效的
function f() {
return Promise.resolve('TEST');
}
async function asyncF() {
return 'TEST';
}
await
1.await 右側(cè)的表達(dá)式一般為 promise 對(duì)象。
2.await 是在等一個(gè)表達(dá)式的結(jié)果,等一個(gè)返回值(回調(diào)成功的內(nèi)容)
3.如果 await 右側(cè)是一個(gè) promise 對(duì)象,則 await 返回的是 promise 成功的值。
注意:
1.await 必須寫在 async 函數(shù)中,但 async 函數(shù)中可以沒有 await。
2.如果 await 的 promise 失敗了,就會(huì)拋出異常,該異常需要通過(guò) try catch 捕獲處理。
3.如果它等到的是一個(gè) Promise 對(duì)象,await 就忙起來(lái)了,它會(huì)阻塞后面的代碼,等著 Promise 對(duì)象 resolve,然后得到 resolve 的值,作為 await 表達(dá)式的運(yùn)算結(jié)果。
正常情況下,await命令后面是一個(gè) Promise對(duì)象,返回該對(duì)象的結(jié)果。如果不是 Promise對(duì)象,就直接返回對(duì)應(yīng)的值
async function f(){ // 等同于 // return 123 return await 123}f().then(v => console.log(v)) // 123
不管await后面跟著的是什么,await都會(huì)阻塞后面的代碼
async function fn1 (){
console.log(1)
await fn2()
console.log(2) // 阻塞
}
async function fn2 (){
console.log('fn2')
}
fn1()
console.log(3)
上面的例子中,await 會(huì)阻塞下面的代碼(即加入微任務(wù)隊(duì)列),先執(zhí)行 async外面的同步代碼,同步代碼執(zhí)行完,再回到 async 函數(shù)中,再執(zhí)行之前阻塞的代碼
所以上述輸出結(jié)果為:1,fn2,3,2
流程分析
通過(guò)對(duì)上面的了解,我們對(duì)JavaScript對(duì)各種場(chǎng)景的執(zhí)行順序有了大致的了解
這里直接上代碼:
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('settimeout')
})
async1()new Promise(function (resolve) {
console.log('promise1')
resolve()}).then(function () {
console.log('promise2')
})
console.log('script end')
分析過(guò)程:
- 執(zhí)行整段代碼,遇到 console.log('script start') 直接打印結(jié)果,輸出 script start
- 遇到定時(shí)器了,它是宏任務(wù),先放著不執(zhí)行
- 遇到 async1(),執(zhí)行 async1 函數(shù),先打印 async1 start,下面遇到await怎么辦?先執(zhí)行 async2,打印 async2,然后阻塞下面代碼(即加入微任務(wù)列表),跳出去執(zhí)行同步代碼
- 跳到 new Promise 這里,直接執(zhí)行,打印 promise1,下面遇到 .then(),它是微任務(wù),放到微任務(wù)列表等待執(zhí)行
- 最后一行直接打印 script end,現(xiàn)在同步代碼執(zhí)行完了,開始執(zhí)行微任務(wù),即 await下面的代碼,打印 async1 end
- 繼續(xù)執(zhí)行下一個(gè)微任務(wù),即執(zhí)行 then 的回調(diào),打印 promise2
- 上一個(gè)宏任務(wù)所有事都做完了,開始下一個(gè)宏任務(wù),就是定時(shí)器,打印 settimeout
所以最后的結(jié)果是:script start、async1 start、async2、promise1、script end、async1 end、promise2、settimeout
題目如下:
async function async1(){
console.log('1')
await async2()
console.log('2')
}
async function async2(){
console.log('3')
}
console.log('4')
setTimeout(function(){
console.log('5')
},0)
async1();
new Promise(function(resolve){
console.log('6')
resolve();
}).then(function(){
console.log('7')
})
console.log('8')
答案如下:
。。。。。。。。