熟悉ES6語法的同窗們確定對Generator(生成器)函數不陌生,這是一個化異步爲同步的利器。
栗子:javascript
function* abc() { let count = 0; while(true) { let msg = yield ++count; console.log(msg); } } let iter = abc(); console.log(iter.next().value); // 1 console.log(iter.next('abc').value); // 'abc' // 2
首先,咱們先簡單回顧一下JS的運行規則:java
那麼,Generator函數是如何進行異步化爲同步操做的呢?
實質上很簡單,* 和 yield 是一個標識符,在瀏覽器進行軟編譯的時候,遇到這兩個符號,自動進行了代碼轉換:ajax
// 異步函數 function asy() { $.ajax({ url: 'test.txt', dataType: 'text', success() { console.log("我是異步代碼"); } }) } function* gener() { let asy = yield asy(); yield console.log("我是同步代碼"); } let it = gener().next(); it.then(function() { it.next(); }) // 我是異步代碼 // 我是同步代碼
// 瀏覽器編譯以後 function gener() { // let asy = yield asy(); 替換爲 $.ajax({ url: 'test.txt', dataType: 'text', success() { console.log("我是異步代碼"); // next 以後執行如下 console.log("我是同步代碼"); } }) // yield console.log("我是同步代碼"); }
整個過程相似於,瀏覽器遇到標識符 * 以後,就明白這個函數是生成器函數,一旦遇到 yield 標識符,就會將之後的函數放入此異步函數以內,待異步返回結果後再進行執行。promise
更深一步,從內存上來說:瀏覽器
普通函數在被調用時,JS 引擎會建立一個棧幀,在裏面準備好局部變量、函數參數、臨時值、代碼執行的位置(也就是說這個函數的第一行對應到代碼區裏的第幾行機器碼),在當前棧幀裏設置好返回位置,而後將新幀壓入棧頂。待函數執行結束後,這個棧幀將被彈出棧而後銷燬,返回值會被傳給上一個棧幀。異步
當執行到 yield 語句時,Generator 的棧幀一樣會被彈出棧外,但Generator在這裏耍了個花招——它在堆裏保存了棧幀的引用(或拷貝)!這樣當 it.next 方法被調用時,JS引擎便不會從新建立一個棧幀,而是把堆裏的棧幀直接入棧。由於棧幀裏保存了函數執行所需的所有上下文以及當前執行的位置,因此當這一切都被恢復如初之時,就好像程序從本來暫停的地方繼續向前執行了。函數
而由於每次 yield 和 it.next 都對應一次出棧和入棧,因此能夠直接利用已有的棧機制,實現值的傳出和傳入。this
至此,Generator 的魔力已經揭開。url
Promise的用法你們應該都很熟悉:線程
let pr = new Promise(function(resolve, reject) { setTimeout(function() { resolve("成功執行啦"); }, 2000) }) pr.then(function(data) { console.log(data); // 成功執行啦 })
那麼 Promise 是如何實現異步加載的呢?
Promise 並無你們想的那麼神祕,其本質就是一個狀態機。
想要實現一個土生土長的 Promise 其實很簡單,狀態機,咱們須要幾個參數:
下面就手動實現一個 Promise
class Promise1 { constructor(fn) { // 執行隊列 this.__watchList = []; // 成功結果 this.__success_res = null; // 失敗結果 this.__error_res = null; // 狀態 this.__status = ""; fn((...args) => { // 保存成功數據 this.__success_res = args; // 狀態改成成功 this.__status = "success"; // 若爲異步則回頭執行then成功方法 this.__watchList.forEach(element => { element.fn1(...args); }); }, (...args) => { // 保存失敗數據 this.__error_res = args; // 狀態改成失敗 this.__status = "error"; // 若爲異步則回頭執行then失敗方法 this.__watchList.forEach(element => { element.fn2(...args); }); }); } // then 函數 then(fn1, fn2) { if (this.__status === "success") { fn1(...this.__success_res); } else if (this.__status === "error") { fn2(...this.__error_res); } else { this.__watchList.push({ fn1, fn2 }) } } }
這樣就簡單實現了 Promise 的功能,在使用上和JS的 Promise 並沒有其餘區別,若想實現 Promise.all 方法,則只須要進行小小的迭代:
Promise1.all = function(arr) { // 存放結果集 let result = []; return Promise1(function(resolve, reject) { let i = 0; // 進行迭代執行 function next() { arr[i].then(function(res) { // 存放每一個方法的返回值 result.push(res); i++; // 若所有執行完 if (i === result.length) { // 執行then回調 resolve(result); } else { // 繼續迭代 next(); } }, reject) } }) }
至此,Generator 和 Promise 都已解析完成。