美文网首页
Promise源码解析(一)

Promise源码解析(一)

作者: 仧小张 | 来源:发表于2018-11-06 11:08 被阅读0次

Promise很复杂..恩..原来觉得不就是词法作用域吗, 原来觉得词法作用域不就是调用时的堆栈是定义时的嘛 结果...一个简单概念被玩成了这样..

promise.js源码从https://blog.csdn.net/qq_22844483/article/details/73655738

所得 谢谢原作者(未申请转载 啊哈) 原本他的意思是从0写一个promise 但是promise的链式调用部分看着晕晕的 为什么呢 因为正向来考虑像我这种菜鸟想不通啊 也罢 那就逆向来想吧 跟一下堆栈 没成想 两个then就20多个堆栈 一步一步跟我来 反向完了来正向 非把它搞定不可

使用的promise.js是简版 只有resolve而没有其他实现(包括.all/reject等)

先上一段使用代码 两个.then

![图片描述](//img.mukewang.com/5be113d10001170c08070626.jpg)

在最后的console.log打断点 看到的堆栈是这样的

![图片描述](//img.mukewang.com/5be113e70001a3a706980824.jpg)

二十几个啊 咳咳 看看多少同名的玩意....

在promise.js中加了一些有意义的输出 控制台是这样的(图三)

![图片描述](//img.mukewang.com/5be1140c000132e812790680.jpg)

这里声明:

①②③④⑤⑥⑦⑧⑨⑩...为标记 可以通过标记符在很多行的文字解释和代码之间查找 没办法 promise里各种同名不同栈的函数..

上promise.js源码(使用的是https://blog.csdn.net/qq_22844483/article/details/73655738 里的部分代码 转载未申请 好开心)

<pre>

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('\tvalue等于undefined了 还是在resolve内执行');

            if(callbacks.length == 0)

                console.log('\t啥都木有');

            return;

        }

        state = 'fulfilled';

        value = newValue;

        setTimeout(function setTimeoutFuc() {

            callbacks.forEach(function (callback) {

            console.log('pop', callbacks);

            handle(callback);

            });

        }, 0);

    }

    fn(resolve); ⑥

}

</pre>

对照执行代码看下

<pre>

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);

})

</pre>

Promise.js源码中很重要的一条语句就是最后的fn(resolve) [标记6] 这条语句可以让带有参数的Promise在实例化时自动执行他的参数 同时把resolve传入 注意 这里的resolve如果是then的参数的参数 那往往是Promise的上层堆栈的resolve 这个可以从图三里很好的看出来 而最重要的所谓反转的反转的实现 就在这里! 通过handle函数 利用词法作用域 将本层堆栈的对象push到上层callbacks中 以实现不同promise的通信

[标记1]这里是我自己标记的针对不同promise做的ii属性 这样可以在函数调用时 查看它是在哪个promise调用的

[标记2]这个就有趣了 可以用来跟踪所谓通过在函数promise中添加私有属性i 在调用resolve和handle时 可以同时打印resolve和handle所在词法作用域的i属性 从而显示这两个函数是哪个

堆栈调用的^^

开始看代码(图三结合图二)

首先[标记3] a()执行返回promise[这是第一个promise] 继续调用.then 会继续返回promise[第二个promise]同时在then中的function会作为onFulfilled作为后续处理 其实就是通过第二个promise的handle方法(这个handle其实是第一个promise的) 将onFulfilled和第二个promise的resolve push到第一个promise的callbacks中(这里很重要!!!!!!做标记④ handle没有传参 所以是从上级的堆栈调用 而resolve是传参了 那直接从函数作用域调用 故是属于当前promise的resolve) 从图三的第一行可以看到

![图片描述](//img.mukewang.com/5be1148d000156a912700119.jpg)

代码往下走 到达第二个then [标记5] then返回一个promise(第三个promise) 同时then的参数同样作为onFulfilled 和 第三个promise的resolve 进入handle函数push到第二个promise中 从图三的第三四行可以看到

![图片描述](//img.mukewang.com/5be1149f0001a95608890072.jpg)

代码往下走 代码没了..

回过头 在刚刚的a()执行时 不仅返回了promise 而且通过promise的最后一行代码fn(resolve)[标记6]执行了

<pre>

function(resolve, reject){

            setTimeout(function(){

                resolve(1);

            },100);

</pre>

这块的代码 这个函数的执行 会执行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行也可以看到         

![图片描述](//img.mukewang.com/5be114c6000107fc08880040.jpg)

现在开始执行resolve了 也就是下面的这块代码 在上面的代码块中可以找到

<pre>

        state = 'fulfilled';

        value = newValue;

        setTimeout(function setTimeoutFuc() {

            callbacks.forEach(function (callback) {

                console.log('pop', callbacks);    //  <<== 这里很有爱

                handle(callback);

            });

        }, 0);

</pre>

状态(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);"这条语句 所以控制台的第六行显示

![图片描述](//img.mukewang.com/5be114dc00013d9108880028.jpg)

然后 handle开始处理这个callback 也就是一个对象啦

(((((((((((((((((((((((((((各单位注意 高潮开始了))))))))))))))))))))))))))))))

进入handle 现在的堆栈是第一个promise 而之前进行push时[标记4]handle用的是第一个promise的handle 因为作为then的参数中的handle也只能用第一个promise的传入啊 handle代码如下

<pre>

    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);

    }

</pre>

现在进入(第一个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的参数 可参考[标记6] 执行setTimeout方法 结果就是列入队列 此时setTimeout内的的resolve为第四个promise的resolve

下一句非常重要

`

callback.resolve(ret);

`

这个callback里的resolve是第几个promise的resolve? 看[标记4] 是第二个promise的resolve!看真相图

![图片描述](//img.mukewang.com/5be387f50001a14105060042.jpg)

而ret是第四个promise 好的 开始愉快的执行resolve吧

<pre>

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('\tvalue等于undefined了 还是在resolve内执行');

            if(callbacks.length == 0)

                console.log('\t啥都木有');

            return;

        }

        state = 'fulfilled';

        value = newValue;

        setTimeout(function setTimeoutFuc() {

            callbacks.forEach(function (callback) {

            console.log('pop', callbacks);

            handle(callback);

            });

        }, 0);

    }

</pre>

由于ret 也就是这里传进来的newValue是一个promise 进入第一个if 来回来去执行到了

`then.call(newValue, resolve);`

言外之意是 "我要执行第四个promise的then方法哦 而且是把第二个resolve传进去作为onFulfilled哦"

这句代码实在是太经典了 因为之后会重用第二个resolve!

好的 then执行了 返回了第五个promise!

![图片描述](//img.mukewang.com/5be38a72000185c309750023.jpg)

<pre>

this.then = function thenFun(onFulfilled) {

        return new Promisee(function resolveFuc(resolve) {

            handle({

                onFulfilled: onFulfilled || null,

                resolve: resolve

            });

        });

    };

</pre>①②③④⑤⑥⑦⑧⑨⑩

通过第四个promise的handle(不要问我为什么是第四个 因为每个then的参数调用都只能通过作用于向上找到上级promise的handle啊 因为参数没传handle啊 ( ˇˍˇ )) 把第二个resolve(即这里的onFulfilled)和第五个resolve(第五个 wtf!)push到了第四个promise中

注意上面的[标记7] 这里有个return 按时一切的push到此结束了...

回到[标记8]的setTimeout 开始执行队列

`resolve(num*3)`

第四个resolve开始执行 通过闭包向上找到num 也就是[标记9]的value

![图片描述](//img.mukewang.com/5be38c290001d3e506450031.jpg)

此时的数字已经通过num*3 也就是1*3 得到了数字3

然后 把第四个promise中的callbacks逐个用handle处理了 callbacks里有什么 当然是上面几行里刚刚push的第二个promise的resolve和第五个promise的resolve

执行第二个promise的resolve:

`var ret = callback.onFulfilled(value);`

由于resolve里是一个setTimeout 这样开始队列

再执行下一句

`callback.resolve(ret);`

这个resolve是第五个resolve 开始执行 可是第五个promise里面啥也木有

回头开始执行队列

<pre>

setTimeout(function setTimeoutFuc() {

            callbacks.forEach(function (callback) {

            console.log('pop', callbacks);

            handle(callback);

            });

        }, 0);

</pre>

第二个resolve讲第二个promise的callbacks数组 也就是代码阶段第二个then执行push的函数 调出来执行

<pre>

.then(function(num2){

console.log('num2',num2);

})

</pre>

重复的handle代码我就不再贴了

两句重要的如下

`var ret = callback.onFulfilled(value);

        callback.resolve(ret);`

第一句是把

`function(num2){

console.log('num2',num2);

}`

执行 此处的value就是刚才(num*3)得到的 通过闭包获得

这样控制台打印出'num2' 3

![图片描述](//img.mukewang.com/5be390450001233806290029.jpg)

还没有结束 各位老板

还有一句没有执行完

`callback.resolve(ret)`

此时ret是console.log的返回值 即undefined 即使进入resolve也是没有意义的 因为第三个promise里没有压入如何的对象(即没有执行handle) 第三个promise是在哪生成的呢? 大家想想 答案就是执行代码的第二个then 因为then后没有then了 所以也就没有执行那个没有的then里面的handle 也就是说如果继续有then就继续handle继续push继续扯 扯 扯 扯到世界末日( ⊙ o ⊙ )啊!

![图片描述](//img.mukewang.com/5be3912e000133d306650099.jpg)

可以了 完结撒花.

相关文章

网友评论

      本文标题:Promise源码解析(一)

      本文链接:https://www.haomeiwen.com/subject/dzvoxttx.html