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