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

猜你喜欢

转载自www.cnblogs.com/fengchenxiujisd/p/9916561.html