Promise源码解析(一)

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)

可以了 完结撒花.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容

  • 这个很早以前写的,今天看群里有人问关于promise的问题,在这里重新发一下。偷懒的同学可以直接拉到最后有完整的代...
    grain先森阅读 2,915评论 1 9
  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,705评论 1 56
  • 一、Promise的含义 Promise在JavaScript语言中早有实现,ES6将其写进了语言标准,统一了用法...
    Alex灌汤猫阅读 823评论 0 2
  • 本文适用的读者 本文写给有一定Promise使用经验的人,如果你还没有使用过Promise,这篇文章可能不适合你,...
    HZ充电大喵阅读 7,305评论 6 19
  • title: promise总结 总结在前 前言 下文类似 Promise#then、Promise#resolv...
    JyLie阅读 12,239评论 1 21