先看一段代碼
console.log('打印'+1); setTimeout(function(){ console.log('打印'+2); }) new Promise(function(resolve,reject){ console.log('打印'+3); }).then( console.log('打印'+4));; console.log('打印'+10); new Promise(function(resolve,reject){ setTimeout(function () { console.log('打印'+5); }); }).then( console.log('打印'+6)); setTimeout(function(){ new Promise(function(resolve,reject){ console.log('打印'+7); }); })
執行結果:
console.log('打印'+1); setTimeout(function(){ console.log('打印'+2); }) new Promise(function(resolve){ console.log('打印'+3); resolve(); }).then(function(){ console.log(4); } ); console.log('打印'+10); new Promise(function(resolve){ setTimeout(function () { console.log('打印'+5); }); resolve(); }).then(function(){ console.log('打印'+6)}); setTimeout(function(){ new Promise(function(resolve){ console.log('打印'+7); }); }) //執行結果: //1;3;10;4;6;2;5;7
可以看出Promise比setTimeout()先執行。
因為Promise定義之后便會立即執行,其后的.then()是異步里面的微任務。
而setTimeout()是異步的宏任務。
引自https://www.cnblogs.com/woodyblog/p/6061671.html
js是單線程語言,但js的宿主環境(比如瀏覽器,Node)是多線程的,宿主環境通過某種方式(事件驅動,下文會講)使得js具備了異步的屬性。
瀏覽器
js是單線程語言,瀏覽器只分配給js一個主線程,用來執行任務(函數),但一次只能執行一個任務,這些任務形成一個任務隊列排隊等候執行,但前端的某些任務是非常耗時的,比如網絡請求,定時器和事件監聽,如果讓他們和別的任務一樣,都老老實實的排隊等待執行的話,執行效率會非常的低,甚至導致頁面的假死。所以,瀏覽器為這些耗時任務開辟了另外的線程,主要包括http請求線程,瀏覽器定時觸發器,瀏覽器事件觸發線程,這些任務是異步的。
任務隊列
剛才說到瀏覽器為網絡請求這樣的異步任務單獨開了一個線程,那么問題來了,這些異步任務完成后,主線程怎么知道呢?答案就是回調函數,整個程序是事件驅動的,每個事件都會綁定相應的回調函數,舉個栗子,有段代碼設置了一個定時器
setTimeout(function(){ console.log(time is out); },1000);
執行這段代碼的時候,瀏覽器異步執行計時操作,當1000ms到了后,會觸發定時事件,這個時候,就會把回調函數放到任務隊列里。整個程序就是通過這樣的一個個事件驅動起來的。
所以說,js是一直是單線程的,瀏覽器才是實現異步的那個家伙。
導圖要表達的內容用文字來表述的話:
- 同步和異步任務分別進入不同的執行"場所",同步的進入主線程,異步的進入Event Table並注冊函數。
- 當指定的事情完成時,Event Table會將這個函數移入Event Queue。
- 主線程內的任務執行完畢為空,會去Event Queue讀取對應的函數,進入主線程執行。
- 上述過程會不斷重復,也就是常說的Event Loop(事件循環)。
主線程
js一直在做一個工作,就是從任務隊列里提取任務,放到主線程里執行。下面我們來進行更深一步的理解。
(1)所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。
(2)主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
(3)一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看里面有哪些事件。那些對應的異步任務,於是結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重復上面的第三步。
只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。這個過程會不斷重復。
Event Loop
主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為Event Loop(事件循環)。
為了更好地理解Event Loop,請看下圖(轉引自Philip Roberts的演講《Help, I'm stuck in an event-loop》)。
上圖中,主線程運行的時候,產生堆(heap)和棧(stack),棧中的代碼調用各種外部API,它們在"任務隊列"中加入各種事件(click,load,done)。只要棧中的代碼執行完畢,主線程就會去讀取"任務隊列",依次執行那些事件所對應的回調函數。
異步任務有宏任務和微任務。
2.宏任務macrotask:
(事件隊列中的每一個事件都是一個macrotask)
優先級:主代碼塊 > setImmediate > MessageChannel > setTimeout / setInterval
比如:setImmediate指定的回調函數,總是排在setTimeout前面
3.微任務包括:
優先級:process.nextTick > Promise > MutationObserver
下面這個代碼輸出結果是什么?
主程序和和settimeout都是宏任務,兩個promise是微任務
第一個宏任務(主程序)執行完,執行全部的微任務(兩個promise),再執行下一個宏任務(settimeout),所以結果為:
執行結果: