既然說JavaScript是單線程,那就是在執行代碼的時候是從上往下執行的,先來看一段代碼:javascript
setTimeout(function(){
console.log('定時器開始')
});
new Promise(function(resolve){
console.log('Promise開始');
resolve();
}).then(function(){
console.log('執行then函數')
});
console.log('代碼執行結束');
複製代碼
輸出結果:html
console.log("A");
while(true){ }
console.log("B");
請問最後的輸出結果是什麼?
複製代碼
若是你的回答是A,恭喜你答對了,由於這是同步任務,程序由上到下執行,遇到while()死循環,下面語句就沒辦法執行。前端
console.log("A");
setTimeout(function(){
console.log("B");
},0);
while(true){}
請問最後的輸出結果是什麼?
複製代碼
若是你的答案是A,恭喜你如今對js運行機制已經有個粗淺的認識了!題目中的setTimeout()就是個異步任務。在全部同步任務執行完以前,任何的異步任務是不會執行的。html5
JavaScript是一門單線程語言,在最新的HTML5中提出了WebWorker, 但JavaScript是單線程這一核心扔未改變。因此一切JavaScript版的「多線程」都是用單線程模擬出來的,一切JavaScript多線程都是紙老虎java
最初設計JS是用來在瀏覽器驗證表單操控DOM元素的是一門腳本語言,若是js是多線程的,那麼兩個線程同時對一個DOM元素進行了相互衝突的操做,那麼瀏覽器的解析器是沒法執行的。node
若是JavaScript中不存在異步,只能自上而下執行,若是上一行解析時間很長,那麼下面的代碼就會被阻塞。 對於用戶而言,阻塞就覺得着「卡死」,這樣就致使了不好的用戶體驗。好比在進行Ajax請求的時候若是沒有返回數據後面的代碼就沒辦法執行面試
JavaScript中的異步以及多線程均可以理解成爲一種「假象」,就拿h5的WebWorker來講,子線程有諸多限制,不能控制DOM,不能修改全局對象等等,一般只用來作計算作數據處理。 這些限制並無違背咱們以前的觀點,因此說是「假象」。JavaScript異步的執行機制其實就是事件循環(eventloop),理解了eventloop機制,就理解了JavaScript異步的執行機制。ajax
事件循環、eventloop、運行機制 這三個術語其實說的是同一個東西, 「先執行同步操做,而後把異步操做排在事件隊列裏,等同步操做都運行完了(運行棧空閒),按順序運行事件隊列裏的內容」這樣的理解其實也沒有任何問題但若是深刻的話會引出不少其餘概念,好比event table和event queue, 咱們來看運行過程:promise
那怎麼知道主線程執行棧爲空啊?JavaScript引擎存在monitoring process進程,會持續不斷的檢查主線程執行棧是否爲空,一旦爲空,就會去event queue那裏檢查是否有等待被調用的函數。瀏覽器
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('發送成功!');
}
})
console.log('代碼執行結束');
複製代碼
上述代碼的運行機制以下:
setTimeout(() => {
console.log('2秒到了')
}, 2000)
複製代碼
setTimeout是異步操做首先進入event table, 註冊的事件就是它的回調,觸發條件就是2秒以後,當知足條件回調被推入event queue,當主線程空閒時會去event queue裏查看是否有可執行的任務。
console.log(1) // 同步任務進入主線程
setTimeout(fun(),0) // 異步任務,被放入event table, 0秒以後被推入event queue裏
console.log(3) // 同步任務進入主線程
複製代碼
一、3是同步任務立刻會被執行,執行完成以後主線程空閒去event queue(事件隊列)裏查看是否有任務在等待執行,這就是爲何setTimeout的延遲事件是0毫秒卻在最後執行的緣由
但setTimeout延時的時間有時候並非那麼準確
setTimeout(() => {
console.log('2秒到了')
}, 2000)
wait(9999999999)
複製代碼
分析運行過程:
其實延遲2秒只是表示2秒後,setTimeout裏的函數被推入event queue , 而event queue(事件隊列)裏的任務,只有在主線程空閒時纔會執行。 上述流程走完,咱們知道setTimeout這個函數,是通過指定時間後,把要執行的任務(本例子中爲console)加入到event queue中, 又由於單線程任務要一個一個執行,若是前面的任務須要的時間過久,那麼只能等着,致使真正的延遲時間遠遠大於2秒。 咱們還常常遇到setTimeout(fn,0)這樣的代碼,它的含義是,指定某個任務在主線程最先的空閒時間執行,意思就是不用再等多少秒了, 只要主線程執行棧內的同步任務所有執行完成,棧爲空就立刻執行。可是即使主線程爲空,0毫秒實際上也是達不到的。根據HTML的標準,最低是4毫秒
以setIntval(fn,ms)爲例,setIntval是循環執行的,setIntval會每隔指定的時間將註冊的函數置入event queue,不是每過ms會執行一次fn,而是每過ms秒,會有fn進入event queue。須要注意一點的是,一但setIntval的回調函數fn執行時間超過了延遲事件ms,那麼就完成看不出來有時間間隔了。
script(全局任務)
、setTimeout、setInterval、MessageChannel、postMessage、setImmediate、I/O、UI rendering在劃分宏任務、微任務的時候並無提到async/ await,由於async/await的本質就是Promise
一句話歸納上面的流程圖:當某個宏任務隊列的中的任務所有執行完之後,會查看是否有微任務隊列。若是有,先執行微任務隊列中的全部任務,若是沒有,就查看是否有其餘宏任務隊列。
接下來咱們看兩道例子來介紹上面流程:
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
},0)
})
setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve().then(()=>{
console.log('Promise2')
})
},0)
複製代碼
最後輸出結果是Promise1,setTimeout1,Promise2,setTimeout2
console.log('----------------- start -----------------');
setTimeout(() => {
console.log('setTimeout');
}, 0)
new Promise((resolve, reject) =>{
for (var i = 0; i < 5; i++) {
console.log(i);
}
resolve(); // 修改promise實例對象的狀態爲成功的狀態
}).then(() => {
console.log('promise實例成功回調執行');
})
console.log('----------------- end -----------------');
複製代碼
不一樣類型的任務會進入對應的event queue, 好比setTime和setIntval會進入相同(宏任務)的event queue, 而Promise和process.nextTick會進入相同(微任務)的event queue. 其中,process.nextTick(callback) 相似node.js版的"setTimeout",在事件循環的下一次循環中調用 callback 回調函數。
Promise在初始化時,傳入的函數是同步執行的,而後註冊then回調。註冊完以後,繼續往下執行同步代碼,在這以前,then的回調不會執行。同步代碼塊執行完畢後,纔會在事件循環中檢測是否有可用的promise回調,若是有,那麼執行,若是沒有,繼續下一個事件循環。
下面用代碼來深刻理解上面的機制:
setTimeout(function() {
console.log('4')
})
new Promise(function(resolve) {
console.log('1') // 同步任務
resolve()
}).then(function() {
console.log('3')
})
console.log('2')
複製代碼
console.log('1')
setTimeout(function() {
console.log('2')
process.nextTick(function() {
console.log('3')
})
new Promise(function(resolve) {
console.log('4')
resolve()
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6')
})
new Promise(function(resolve) {
console.log('7')
resolve()
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9')
process.nextTick(function() {
console.log('10')
})
new Promise(function(resolve) {
console.log('11')
resolve()
}).then(function() {
console.log('12')
})
})
複製代碼
若是是setTimeout裏面嵌套setTimeout, 那麼嵌套的setTimeout的宏任務要在外面的宏任務排序的後面,日後排。看個例子
new Promise(function (resolve) {
console.log('1')// 宏任務一
resolve()
}).then(function () {
console.log('3') // 宏任務一的微任務
})
setTimeout(function () { // 宏任務二
console.log('4')
setTimeout(function () { // 宏任務五
console.log('7')
new Promise(function (resolve) {
console.log('8')
resolve()
}).then(function () {
console.log('10')
setTimeout(function () { // 宏任務七
console.log('12')
})
})
console.log('9')
})
})
setTimeout(function () { // 宏任務三
console.log('5')
})
setTimeout(function () { // 宏任務四
console.log('6')
setTimeout(function () { // 宏任務六
console.log('11')
})
})
console.log('2') // 宏任務一
複製代碼
初步總結: 宏任務是一個棧按先入先執行的原則,微任務也是一個棧也是先入先執行。可是每一個宏任務都對應會有一個微任務棧,宏任務在執行過程當中會先執行同步代碼再執行微任務棧。
咱們建立了promise但不能同步等待它執行完成。 咱們只能經過then傳一個回調函數這樣很容易再次陷入promise的回調地獄。 實際上, async/await在底層轉換成了promise和then回調函數,也就是說, 這是promise的語法糖。每次咱們使用await, 解釋器都建立一個promise對象,而後把剩下的async函數中的操做放到then回調函數中。 async/await的實現,離不開promise. 從字面意思來理解, async是「異步」的簡寫,而await是async wait的簡寫能夠認爲是等待異步方法執行完成。
用來優化promise的回調問題,被稱爲是異步的終極解決方案
async函數會返回一個Promise對象,若是在函數中return一個直接量(普通變量),async會把這個直接量經過Promise.resolve()封裝成Promise對象。若是你返回了promise那就以你返回的promise爲準。await是在等待,等待運行的結果也就是返回值。await後面一般是一個異步操做(promise),可是這不表明await後面只能跟異步操做,await後面實際是能夠接普通函數調用或者直接量。 async至關於 new Promise,await至關於then
若是await後面跟的不是一個promise,那await後面表達式的運算結果就是它等到的東西,若是await後面跟的是一個promise對象,await它會'阻塞'後面的diamante,等着promise對象resolve, 而後獲得resolve的值做爲await表達式的運算結果。可是此"阻塞"非彼「阻塞」,這就是await必須用在async函數中的緣由。 async函數調用不會形成"阻塞",它內部全部的「阻塞」都被封裝在一個promise對象中異步執行(這裏的阻塞理解成異步等待更合理)
每一個async方法都返回一個promise, await只能出如今async函數中
單一的promise鏈並不能發現async/await的優點,可是若是須要處理由多個promise組成的then鏈的時候,優點就能體現出來了(Promise經過then鏈來解決多層回調的問題,如今又用async/awai來進一步優化它)
假設一個業務,分多個步驟完成,每一個步驟都是異步的且依賴於上一個步驟的結果
function myPromise(n) {
return new Promise(resolve => {
console.log(n)
setTimeout(() => resolve(n+1), n)
})
}
function step1(n) {
return myPromise(n)
}
function step2(n) {
return myPromise(n)
}
function step3(n) {
return myPromise(n)
}
//若是用 Promise 實現
step1(1000)
.then(a => step2(a))
.then(b => step3(b))
.then(result => {
console.log(result)
})
//若是用 async/await 來實現呢
async function myResult() {
const a = await step1(1000)
const b = await step2(a)
const result = await step3(b)
return result
}
myResult().then(result => {
console.log(result)
}).catch(err => {
// 若是myResult內部有語法錯誤會觸發catch方法
})
複製代碼
看的出來async/await的寫法更多優雅一些要比promise的連接調用更多直觀也易於維護
咱們來看在任務隊列中async/await的運行機制,先給出大概方向再經過案例來證實:
setTimeout(function () {
console.log('6')
}, 0)
console.log('1')
async function async1() {
console.log('2')
await async2()
console.log('5')
}
async function async2() {
console.log('3')
}
async1()
console.log('4')
複製代碼
上述的輸出結果爲:1,2,3,4,5,6
setTimeout(function () {
console.log('6')
}, 0)
console.log('1')
async function async1 () {
console.log('2')
console.log(await async2())
console.log('5')
}
async function async2 () {
console.log('3')
return '11111'
}
async1()
console.log('4')
console.log('888')
console.log('999')
複製代碼
上述的輸出結果爲:1,2,3,4,888,999,11111,5,6
測試代碼的輸出結果,看到async1函數輸出2, 立馬執行await的async2函數,輸出3, 可是沒有當即返回,而是先執行async2外面的同步代碼,最後獲得返回值11111給await async2函數
console.log('1')
async function async1() {
console.log('2')
await 'await的結果'
console.log('5')
}
async1()
console.log('3')
new Promise(function (resolve) {
console.log('4')
resolve()
}).then(function () {
console.log('6')
})
複製代碼
上述的輸出結果爲:1,2,3,4,5,6
async function async1() {
console.log('2')
await async2()
console.log('6')
}
async function async2() {
console.log('3')
}
setTimeout(function () {
console.log('8')
}, 0)
console.log('1')
async1()
new Promise(function (resolve) {
console.log('4')
resolve()
}).then(function () {
console.log('7')
})
console.log('5')
複製代碼
上述的輸出結果爲:1,2,3,4,5,6,7,8
async function async1() {
console.log('2')
const data = await async2()
console.log(data)
console.log('8')
}
async function async2() {
return new Promise(function (resolve) {
console.log('3')
resolve('await的結果')
}).then(function (data) {
console.log('6')
return data
})
}
console.log('1')
setTimeout(function () {
console.log('9')
}, 0)
async1()
new Promise(function (resolve) {
console.log('4')
resolve()
}).then(function () {
console.log('7')
})
console.log('5')
複製代碼
執行結果: 1-2-3-4-5-6-7-await的結果-8-9
setTimeout(function () {
console.log('8')
}, 0)
async function async1() {
console.log('1')
const data = await async2()
console.log('6')
return data
}
async function async2() {
return new Promise(resolve => {
console.log('2')
resolve('async2的結果')
}).then(data => {
console.log('4')
return data
})
}
async1().then(data => {
console.log('7')
console.log(data)
})
new Promise(function (resolve) {
console.log('3')
resolve()
}).then(function () {
console.log('5')
})
複製代碼
完整輸出結果:1-2-3-4-5-6-7-async2的結果-8
題目一
setTimeout(function() {
console.log('4')
})
new Promise(function(resolve) {
console.log('1') // 同步任務
resolve()
}).then(function() {
console.log('3')
})
console.log('2')
複製代碼
輸出結果: 1-2-3-4
題目二
console.log('1')
setTimeout(function() {
console.log('2')
process.nextTick(function() {
console.log('3')
})
new Promise(function(resolve) {
console.log('4')
resolve()
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6')
})
new Promise(function(resolve) {
console.log('7')
resolve()
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9')
process.nextTick(function() {
console.log('10')
})
new Promise(function(resolve) {
console.log('11')
resolve()
}).then(function() {
console.log('12')
})
複製代碼
輸出結果:1-7-6-8-2-4-3-5-9-11-10-12
題目三
setTimeout(function () {
console.log('6')
}, 0)
console.log('1')
async function async1() {
console.log('2')
await async2()
console.log('5')
}
async function async2() {
console.log('3')
}
async1()
console.log('4')
複製代碼
輸出結果:1-2-3-4-5-6
題目四
console.log('1')
async function async1() {
console.log('2')
await 'await的結果'
console.log('5')
}
async1()
console.log('3')
new Promise(function (resolve) {
console.log('4')
resolve()
}).then(function () {
console.log('6')
})
複製代碼
輸出結果:1-2-3-4-5-6