Promise原始碼解析(一) 詞法作用域的那些事
Promise很複雜…恩…原來覺得不就是詞法作用域嗎, 原來覺得詞法作用域不就是呼叫時的堆疊是定義時的嘛 結果…一個簡單概念被玩成了這樣…
promise.js原始碼從https://blog.csdn.net/qq_22844483/article/details/73655738
所得 謝謝原作者(未申請轉載 啊哈) 原本他的意思是從0寫一個promise 但是promise的鏈式呼叫部分看著暈暈的 為什麼呢 因為正向來考慮像我這種菜鳥想不通啊 也罷 那就逆向來想吧 跟一下堆疊 沒成想 兩個then就20多個堆疊 一步一步跟我來 反向完了來正向 非把它搞定不可
使用的promise.js是簡版 只有resolve而沒有其他實現(包括.all/reject等)
先上一段使用程式碼 兩個.then
在最後的console.log打斷點 看到的堆疊是這樣的
二十幾個啊 咳咳 看看多少同名的玩意…
在promise.js中加了一些有意義的輸出 控制檯是這樣的(圖三)
這裡宣告:
①②③④⑤⑥⑦⑧⑨⑩…為標記 可以通過標記符在很多行的文字解釋和程式碼之間查詢 沒辦法 promise裡各種同名不同棧的函式…
上promise.js原始碼(使用的是https://blog.csdn.net/qq_22844483/article/details/73655738 裡的部分程式碼 轉載未申請 好開心)
var i = 0; function Promisee(fn) { var state = 'pending', value = null, callbacks = [], aa = ++i; ② Promisee.ii = i, ① this.then = function thenFun(onFulfilled) { return new Promisee(function resolveFuc(resolve) { handle({ onFulfilled: onFulfilled || null, resolve: resolve }); }); }; function handle(callback) { if (state === 'pending') { callbacks.push(callback); console.log('狀態是pending',"這是Promisee第",handle.caller.caller.ii,"次執行時執行的handle方法 當然 handle是在Promisee第", aa,"次定義的");//說明呼叫handle的是this.then中的Promisee console.log('push', callbacks); return; } //如果then中沒有傳遞任何東西 if(!callback.onFulfilled) { callback.resolve(value); return; } console.log('狀態是fulfilled','這是第',aa,'個Promisee裡的handle'); var ret = callback.onFulfilled(value); callback.resolve(ret); } function resolve(newValue) { console.log('這是第',aa,'個Promisee裡的resolve'); if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { var then = newValue.then; if (typeof then === 'function') { then.call(newValue, resolve); return; } } if(newValue == undefined){ console.log(' value等於undefined了 還是在resolve內執行'); if(callbacks.length == 0) console.log(' 啥都木有'); return; } state = 'fulfilled'; value = newValue; setTimeout(function setTimeoutFuc() { callbacks.forEach(function (callback) { console.log('pop', callbacks); handle(callback); }); }, 0); } fn(resolve); ⑥ }
對照執行程式碼看下
var a = function(){ return new Promisee(function(resolve, reject){ setTimeout(function(){ resolve(1); },100); }) } a().then(function(num){ ③ return new Promisee(function(resolve){ setTimeout(function(){ console.log('這是第二個setTimeout方法 已經開始執行'); resolve(num*3); },100) }) }) .then(function(num2){ ⑤ console.log('num2',num2); })
Promise.js原始碼中很重要的一條語句就是最後的fn(resolve) [標記] 這條語句可以讓帶有引數的Promise在例項化時自動執行他的引數 同時把resolve傳入 注意 這裡的resolve如果是then的引數的引數 那往往是Promise的上層堆疊的resolve 這個可以從圖三裡很好的看出來 而最重要的所謂反轉的反轉的實現 就在這裡! 通過handle函式 利用詞法作用域 將本層堆疊的物件push到上層callbacks中 以實現不同promise的通訊
[標記①]這裡是我自己標記的針對不同promise做的ii屬性 這樣可以在函式呼叫時 檢視它是在哪個promise呼叫的
[標記②]這個就有趣了 可以用來跟蹤所謂通過在函式promise中新增私有屬性i 在呼叫resolve和handle時 可以同時列印resolve和handle所在詞法作用域的i屬性 從而顯示這兩個函式是哪個
堆疊呼叫的^^
開始看程式碼(圖三結合圖二)
首先[標記③] a()執行返回promise[這是第一個promise] 繼續呼叫.then 會繼續返回promise[第二個promise]同時在then中的function會作為onFulfilled作為後續處理 其實就是通過第二個promise的handle方法(這個handle其實是第一個promise的) 將onFulfilled和第一個promise的resolve push到第一個promise的callbacks中(這裡很重要!!!做標記④ 因為handle和resolve的詞法作用域都是在第一個promise中) 從圖三的第一行可以看到
程式碼往下走 到達第二個then [標記⑤] then返回一個promise(第三個promise) 同時then的引數同樣作為onFulfilled 進入handle函式push到第二個promise中 從圖三的第三四行可以看到
程式碼往下走 程式碼沒了…
回過頭 在剛剛的a()執行時 不僅返回了promise 而且通過promise的最後一行程式碼fn(resolve)[標記⑥]執行了
function(resolve, reject){ setTimeout(function(){ resolve(1); },100);
這塊的程式碼 這個函式的執行 會執行setTimeout 進行佇列排隊(意思就是如果有其他程式碼執行就先執行下面的程式碼 如果下面的程式碼有setTimeout就接著這個setTimeout排隊 如果沒有其他程式碼執行了就開始從頭執行setTimeout) 然而在執行完兩個then後 回過頭來開始要執行這個setTimeout了 因為其他程式碼都執行完了 promise.js裡面的不是執行程式碼 圖一的才是 ok
好的 這個倒序整的…
現在setTimeout執行了 等待100毫秒後(這個100毫秒其實是從註冊了setTimeout就開始算起的) 開始執行resolve(1); 這個resolve是第幾個promise的resolove? 第一個! 因為一上來就執行"a()" 而a裡面就是很單純的將promise.js裡倒數27行的resolve傳到了最後的fn(resolve)裡(或者說最後的fn(resolve)裡的resolve就是promise.js裡倒數第27行的resolve) ok? 真是 太單純了 再也找不到這麼單純的了 好難的 555… 從圖三的第5行也可以看到
現在開始執行resolve了 也就是下面的這塊程式碼 在上面的程式碼塊中可以找到
state = 'fulfilled'; value = newValue; setTimeout(function setTimeoutFuc() { callbacks.forEach(function (callback) { console.log('pop', callbacks); // <<== 這裡很有愛 handle(callback); }); }, 0);
狀態(state)被改變成了fulfilled 然後開始setTimeout佇列 問題現在其他程式碼都執行完了 setTimeout直接開始執行了 這裡面的callbacks是第幾個promise的呢 答案是第一個 因為resolve是第一個promise的
而第一個promise的resolve只能找到第一個promise裡的資料 如果是第二個promise的resolve 或者handle就可以訪問第二個promise或者第一個promise的資料 這是作用域的概念 ok
至於callbacks裡面的資料 實際上是第一個then裡面的handle push到第一個promise的callbacks裡面的資料 由於在promise.js裡寫了"console.log(‘pop’, callbacks);"這條語句 所以控制檯的第六行顯示
然後 handle開始處理這個callback 也就是一個物件啦
(((((((((((((((((((((((((((各單位注意 高潮開始了))))))))))))))))))))))))))))))
進入handle 現在的堆疊是第一個promise 而之前進行push時[標記④]handle用的是第一個promise的handle 因為作為then的引數中的handle也只能用第一個promise的傳入啊 handle程式碼如下
function handle(callback) { if (state === 'pending') { callbacks.push(callback); console.log('狀態是pending',"這是Promisee第",handle.caller.caller.ii,"次執行時執行的handle方法 當然 handle是在Promisee第", aa,"次定義的");//說明呼叫handle的是this.then中的Promisee console.log('push', callbacks); return; } //如果then中沒有傳遞任何東西 if(!callback.onFulfilled) { callback.resolve(value); return; } console.log('狀態是fulfilled','這是第',aa,'個Promisee裡的handle'); var ret = callback.onFulfilled(value); callback.resolve(ret); }
okokokokokokok
現在進入(第一個promise的)handle函式 由於狀態(第一個promise的state)已經改為fulfilled 故直接執行callback.onFulfilled(value); value即是第一個執行的resolve傳入的引數1 onFulfilled是then傳入的函式引數
function(num){
return new Promisee(function(resolve){
setTimeout(function(){
console.log(‘這是第二個setTimeout方法 已經開始執行’);
resolve(num*3);
},100)
})
}①②③④⑤⑥⑦⑧⑨⑩
執行後 返回一個promise 注意 此時會同時執行此promise的引數 可參考[標記⑥] 此時的resolve為第二個promise的resolve