javascript 是單線程執行的,由js文件自上而下依次執行。即爲同步執行,如果有網絡請求或者定時器等業務時,不能讓瀏覽器傻傻等待到結束後再繼續執行後面的js吧!因此js設計了異步模式!javascript
下面是一個常見的定時器與promise的問題:java
setTimeout(() => { console.log('我是第一個宏任務'); Promise.resolve().then(() => { console.log('我是第一個宏任務裏的第一個微任務'); }); Promise.resolve().then(() => { console.log('我是第一個宏任務裏的第二個微任務'); }); }, 0); setTimeout(() => { console.log('我是第二個宏任務'); }, 0); Promise.resolve().then(() => { console.log('我是第一個微任務'); }); console.log('執行同步任務');
執行結果以下:promise
爲何是這種執行結果?瀏覽器
這就要說到js的執行機制:事件循環(event loop)!網絡
當JS解析執行時,會被引擎分爲兩類任務,同步任務(synchronous) 和 異步任務(asynchronous)。異步
對於同步任務來講,會被推到執行棧按順序去執行這些任務。
對於異步任務來講,當其能夠被執行時,會被放到一個 任務隊列(task queue) 裏等待JS引擎去執行。async
當執行棧中的全部同步任務完成後,JS引擎纔會去任務隊列裏查看是否有任務存在,並將任務放到執行棧中去執行,執行完了又會去任務隊列裏查看是否有已經能夠執行的任務。這種循環檢查的機制,就叫作事件循環(Event Loop)。函數
對於任務隊列,實際上是有更細的分類。其被分爲 微任務(microtask)隊列 & 宏任務(macrotask)隊列
宏任務: setTimeout、setInterval等,會被放在宏任務(macrotask)隊列。
微任務: Promise的then、Mutation Observer等,會被放在微任務(microtask)隊列。
1.首先執行執行棧裏的任務。
2.執行棧清空後,檢查微任務(microtask)隊列,將可執行的微任務所有執行。
3.取宏任務(macrotask)隊列中的第一項執行。
4.回到第二步。oop
如今咱們知道了爲何定時器會晚於promise執行了。下面咱們討論一下微任務的幾種實現狀況:Promsie、Generator、async/await。spa
===Promsie===
Promise對象是一個構造函數,用來生成Promise實例;
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 異步操做成功 */){ resolve(value); } else { reject(error); } });
Promise 新建後就會當即執行。
let promise = new Promise(function(resolve, reject) { console.log('Promise'); resolve(); }); promise.then(function() { console.log('resolved.'); }); console.log('Hi!'); // Promise // Hi! // resolved
Promise 對象的錯誤具備「冒泡」性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤老是會被下一個catch語句捕獲。
// bad promise .then(function(data) { // success }, function(err) { // error }); // good promise .then(function(data) { //cb // success }) .catch(function(err) { // error });
跟傳統的try/catch代碼塊不一樣的是,若是沒有使用catch方法指定錯誤處理的回調函數,Promise 對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應。
===Generator===
Generator 函數是一個狀態機,封裝了多個內部狀態。
執行 Generator 函數會返回一個遍歷器對象,也就是說,Generator 函數除了狀態機,仍是一個遍歷器對象生成函數。返回的遍歷器對象,能夠依次遍歷 Generator 函數內部的每個狀態。
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator();
===async===
async 函數是什麼?一句話,它就是 Generator 函數的語法糖。
(1)內置執行器。
Generator 函數的執行必須靠執行器,因此纔有了co模塊,而async函數自帶執行器。也就是說,async函數的執行,與普通函數如出一轍,只要一行。
asyncReadFile();
上面的代碼調用了asyncReadFile函數,而後它就會自動執行,輸出最後結果。這徹底不像 Generator 函數,須要調用next方法,或者用co模塊,才能真正執行,獲得最後結果。
(2)更好的語義。
async和await,比起星號和yield,語義更清楚了。async表示函數裏有異步操做,await表示緊跟在後面的表達式須要等待結果。
(3)更廣的適用性。
co模塊約定,yield命令後面只能是 Thunk 函數或 Promise 對象,而async函數的await命令後面,能夠是 Promise 對象和原始類型的值(數值、字符串和布爾值,但這時會自動轉成當即 resolved 的 Promise 對象)。
(4)返回值是 Promise。
async函數的返回值是 Promise 對象,這比 Generator 函數的返回值是 Iterator 對象方便多了。你能夠用then方法指定下一步的操做。
進一步說,async函數徹底能夠看做多個異步操做,包裝成的一個 Promise 對象,而await命令就是內部then命令的語法糖。