Js中process.nextTick,setImmediate,setTimeout,Promise.then,async/await終極非同步執行順序全解析
雖然大家知道async/await,但是很多人對這個方法中內部怎麼執行的還不是很瞭解
await做了什麼處理
從字面意思上看await就是等待,await 等待的是一個表示式,這個表示式的返回值可以是一個promise物件也可以是其他值。
很多人以為await會一直等待之後的表示式執行完之後才會繼續執行後面的程式碼
,實際上await是一個讓出執行緒的標誌。await後面的函式會先執行一遍,然後就會跳出整個async函式來執行後面js棧後面的程式碼。等本輪事件迴圈執行完了之後又會跳回到async函式中等待await後面表示式的返回值,如果返回值為非promise則繼續執行async函式後面的程式碼,否則將返回的promise放入promise佇列(Promise的Job Queue)
證明如下:
例子1:
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
function async2() {
console.log('async2')
}
async1()
console.log("666")
結果:
例子2:
async function async1() {
console.log('async1 start')
let res = await async2()
console.log(res)
console.log('async1 end')
}
function async2() {
return 1
}
async1()
console.log("666")
結果:
再來難一些的:
例子3:
function testSometing() {
console.log("執行testSometing");
return "testSometing"
}
async function testAsync() {
console.log("執行testAsync");
return Promise.resolve("hello async");
}
async function test() {
console.log("test start...");
const v1 = await testSometing(); //關鍵點1
console.log(v1);
const v2 = await testAsync();
console.log(v2);
console.log(v1, v2);
}
test();
var promise = new Promise((resolve) => {
console.log("promise start..");
resolve("promise");
}); //關鍵點2
promise.then((val) => console.log(val));
console.log("test end...")
結果:
分析如下:
當test函式執行到
const v1 = await testSometing();
的時候,會先執行testSometing
這個函式列印出“執行testSometing
”的字串,然後因為await會讓出執行緒就會區執行後面的
var promise = new Promise((resolve)=> { console.log("promise start..");
resolve("promise");});//關鍵點2
然後列印出“promise start..”
接下來會把返回的這promise
放入promise
佇列(Promise的Job Queue),繼續執行列印“test end...”
,等本輪事件迴圈執行結束後,又會跳回到async函式中(test函式),等待之前await 後面表示式的返回值,因為testSometing
不是async函式,所以返回的是一個字串“testSometing”
,test函式繼續執行,執行到
const v2 = await testAsync();
和之前一樣又會跳出test函式,執行後續程式碼,此時事件迴圈就到了promise
的佇列,執行promise.then((val)=> console.log(val));then
後面的語句,之後和前面一樣又跳回到test函式繼續執行。
Node.js的Event Loop:有坑
- timers: 執行
setTimeout
和setInterval
的回撥 - pending callbacks: 執行延遲到下一個迴圈迭代的 I/O 回撥
- idle, prepare: 僅系統內部使用
- poll: 檢索新的 I/O 事件;執行與 I/O 相關的回撥。事實上除了其他幾個階段處理的事情,其他幾乎所有的非同步都在這個階段處理。
- check:
setImmediate
在這裡執行 - close callbacks: 一些關閉的回撥函式,如:
socket.on('close', ...)
每個階段都有一個自己的先進先出的佇列,只有當這個佇列的事件執行完或者達到該階段的上限時,才會進入下一個階段。在每次事件迴圈之間,Node.js都會檢查它是否在等待任何一個I/O或者定時器,如果沒有的話,程式就關閉退出了。我們的直觀感受就是,如果一個Node程式只有同步程式碼,你在控制檯執行完後,他就自己退出了。
上面的這個流程說簡單點就是在一個非同步流程裡,setImmediate會比定時器先執行
,我們寫點程式碼來試試:
setTimeout(() => {
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
}, 0);
輸出:
setImmediate
setTimeout
我們來理一下這個流程:
- 外層是一個
setTimeout
,所以執行他的回撥的時候已經在timers
階段了 - 處理裡面的
setTimeout
,因為本次迴圈的timers
正在執行,所以他的回撥其實加到了下個timers
階段 - 處理裡面的
setImmediate
,將它的回撥加入check
階段的佇列 - 外層
timers
階段執行完,進入pending callbacks,idle,
prepare,poll,這幾個佇列都是空的,所以繼續往下 - 到了
check
階段,發現了setImmediate
的回撥,拿出來執行 - 然後是
close callbacks
,佇列是空的,跳過 - 又是
timers
階段,執行我們的console
但是請注意我們上面console.log('setTimeout')
和console.log('setImmediate')
都包在了一個setTimeout
裡面,如果直接寫在最外層會怎麼樣呢?程式碼改寫如下:
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
經過多次實驗,setTimeout
和setImmediate
的輸出順序是不一定的,但是大多數情況,setTimeout
更快。有時setImmediate
會在前面,為啥?
需要告訴大家一件事情,node.js裡面setTimeout(fn, 0)會被強制改為setTimeout(fn,
1),這在官方文件中有說明。(說到這裡順便提下,HTML 5裡面setTimeout最小的時間限制是4ms)。
原理我們都有了,我們來理一下流程:
- 外層同步程式碼一次性全部執行完,遇到非同步API就塞到對應的階段
- 遇到
setTimeout
,雖然設定的是0毫秒觸發,但是被node.js強制改為1毫秒,塞入times
階段 - 遇到
setImmediate
塞入check
階段 - 同步程式碼執行完畢,進入Event Loop
- 先進入times階段,檢查當前時間過去了1毫秒沒有,如果過了1毫秒,滿足setTimeout條件,執行回撥,如果沒過1毫秒,跳過
- 跳過空的階段,進入
check
階段,執行setImmediate
回撥
通過上述流程的梳理: 我們發現關鍵就在這個1毫秒,如果同步程式碼執行時間較長,進入Event Loop
的時候1毫秒已經過了,setTimeout
執行,如果1毫秒還沒到,就先執行了setImmediate
。每次我們執行指令碼時,機器狀態可能不一樣,導致執行時有1毫秒的差距,一會兒setTimeout
先執行,一會兒setImmediate
先執行。但是這種情況只會發生在還沒進入timers
階段的時候。像我們第一個例子那樣,因為已經在timers
階段,所以裡面的setTimeout
只能等下個迴圈了,所以setImmediate
肯定先執行。
process.nextTick,Promise,setTimeout,setImmediate的對比:
setTimeout對比setImmediate
,很坑
setTimeout(function(){
console.log('setTimeout')
})
setImmediate(() => console.log('setImmediate'));
setImmediate(() => console.log('setImmediate'));
setTimeout(function(){
console.log('setTimeout')
})
上面已經說了多數情況下輸出都是:
setTimeout
setImmediate
如果過了1毫秒setTimeout沒加入到timer階段佇列中
,則是
setImmediate
setTimeout
process.nextTick對比Promise
process.nextTick(() => console.log('nextTick'))
let p = new Promise((resolve,reject)=>{
resolve("gfd")
})
p.then(res=>console.log(res))
let p = new Promise((resolve,reject)=>{
resolve("gfd")
})
p.then(res=>console.log(res))
process.nextTick(() => console.log('nextTick'))
輸出都是:
nextTick
gfd
總結:
process.nextTick和Promise都是微任務,但是任務優先順序process.nextTick 高於 Promise。
process.nextTick,Promise 和 setTimeout,setImmediate對比:
其實就是微任務和巨集任務的對比:
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
process.nextTick(() => console.log('nextTick'))
let p = new Promise((resolve, reject) => {
resolve("gfd")
})
p.then(res => console.log(res))
輸出:
nextTick
gfd
setTimeout
setImmediate
沒有爭議,大家都知道的,微任務和巨集任務碰到了會放入各自的任務佇列中,等主執行緒把整體程式碼(算一次巨集任務)執行完後,就會優先調出微任務佇列中的任務到主執行緒中執行
總結:
非同步事件包括本輪和次輪事件迴圈
,本輪迴圈先於次輪迴圈執行,而Promise.then()是本輪事件迴圈,而setTimeout和setInterval是次輪事件迴圈。
本輪迴圈:
process.nextTick,Promise
次輪迴圈:
setTimeout,setInteral,setImmediate
兩道綜合題:
題目一:
process.nextTick(function() {
console.log("nt1");
})
setTimeout(function() {
console.log('st');
}, 0)
new Promise(function(resolve) {
console.log("promise_s");
resolve();
}).then(function(resolve) {
console.log("promise_call");
})
process.nextTick(function() {
console.log("nt2");
})
console.log('end');
輸出:
promise_s
end
nt1
nt2
promise_call
st
題目二: 終極boss
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('setTimeout0')
}, 0)
setTimeout(function() {
console.log('setTimeout3')
}, 3)
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
async1();
new Promise(function(resolve) {
console.log('promise1')
resolve();
console.log('promise2')
}).then(function() {
console.log('promise3')
})
console.log('script end')
有兩個答案: 都是正確的,關鍵看setTimeout(function() {console.log('setTimeout3')}, 3)
是否及時加入到check階段的任務佇列中去
沒有及時加入進來的結果:
script start
async1 start
async2
promise1
promise2
script end
nextTick
async1 end
promise3
setTimeout0
setImmediate
setTimeout3
及時加入進來的結果:
script start
async1 start
async2
promise1
promise2
script end
nextTick
async1 end
promise3
setTimeout0
setTimeout3
setImmediate
以上就是關於我對非同步任務輸出順序的所有的總結了,希望能幫到困惑的你
相關文章
- process.nextTick,Promise.then,setTimeout,setImmediate執行順序Promise
- async await、Promise、setTimeout執行順序AIPromise
- Node 執行順序 nextTick 非同步IO setTimeout setImmediate非同步
- Js中async/await的執行順序詳解JSAI
- 事件迴圈 EventLoop(Promise,setTimeOut,async/await執行順序)事件OOPPromiseAI
- JS中的async/await的執行順序詳解JSAI
- promise、async、await非同步原理與執行順序PromiseAI非同步
- 令人費解的 async/await 執行順序AI
- promise、async和await之執行順序的那點事PromiseAI
- SetTimeout、SetInterVal、setImmediate和process.nextTick的理解
- 細說setTimeout/setImmediate/process.nextTick的區別
- 一道setTimeout async promise執行順序的筆試題引發的思考Promise筆試
- JavaScript同步、非同步、回撥執行順序之經典閉包setTimeout面試題分析JavaScript非同步面試題
- js基礎進階–promise和setTimeout執行順序的問題JSPromise
- ajax非同步導致js方法順序執行不了非同步JS
- js執行順序Event LoopJSOOP
- JUnit 標籤執行順序解析
- 8 張圖幫你一步步看清 async/await 和 promise 的執行順序AIPromise
- 8張圖幫你一步步看清 async/await 和 promise 的執行順序AIPromise
- 8張圖讓你一步步看清 async/await 和 promise 的執行順序AIPromise
- SQL Server中SELECT語句執行順序解析SQLServer
- js解惑-函式執行順序JS函式
- 同步任務與非同步任務執行順序非同步
- 非同步程式設計的終極解決方案 async/await:用同步的方式去寫非同步程式碼非同步程式設計AI
- JS 非同步執行順序 -- 從一道面試題說起JS非同步面試題
- [一道題搞蒙你] - setTimeout 與 Promise 執行順序Promise
- .Net非同步關鍵字async/await的最終理解非同步AI
- JS非同步程式設計之async&awaitJS非同步程式設計AI
- Sql執行順序SQL
- Java中如何保證執行緒順序執行Java執行緒
- SQL 查詢語句的執行順序解析SQL
- 【JavaScript】JS引擎中執行上下文如何順序執行程式碼JavaScriptJS行程
- 類script標籤,非同步載入,順序執行非同步
- 非同步神器async-await非同步AI
- Async and Await 非同步和等待AI非同步
- JS非同步之callback、promise、async+await簡介JS非同步PromiseAI
- JS非同步程式設計 (2) – Promise、Generator、async/awaitJS非同步程式設計PromiseAI
- JS非同步程式設計——深入理解async/awaitJS非同步程式設計AI