1. 程式人生 > >Promise原始碼解析(一) 詞法作用域的那些事

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