resolve 的是一个 promise
then 中 return 了一个 promise
防止循环引用
延迟设计
初衷猜想
关于异常捕获
值的穿透
- pre-notify
- previously :
Promise 深度学习 --- 我 Promise/A + 实现
异步发展简明指北
最近发现掘金上又多了很多 promise 相关的文章, 于是乎几个月前写的东东拿出来看了看, 又理了理, 于是乎就有了这么一篇文
resolve 的是一个 promise
promise 允许 resolve 里可以是一个 promise
比如说
假如我们有一个 promise1, 这个 promise1 resolve 的也是一个 promise, 我们姑且称之为 promise2, 那么这个 promise2 的结果将作为我们 promise1 的结果 (状态和值)
- let p = new Promise(function(resolve,reject){
- resolve(new Promise(function(resolve,reject){
- resolve('a');
- }))
- });
- >>> p.value
- <<< a
并且这个 promise 可以无限的在 resolve 中嵌套下去
而我们只需要记住我们最终拿到的 promise 的结果是 最里层的 promise 的结果 即可
实现
- function resolve(value){
- // value 可能是一个 promise
- if(value!==null&&(typeof value==='object'||typeof value==='function')){
- return value.then(resolve,reject);
- }
- ...
- }
思维模型图大概长这样
就像一个个钩子, 绑定和定义的顺序是从右往左, 执行时是从左往右
then 中 return 了一个 promise
正常情况下如果 then 中 return 的是一个普通值, 那么会走下一个 then 中的成功回调, 并且这个 return 的值会作为回调的参数传入
但如果是一个 promise, 则会根据这个 promise 的状态来决定走下一个 then 中的哪个回调, 并且以这个 promise 的值作为回调的参数传入
- p.then(function(data){
- return new Promise(function(resolve,reject){
- resolve(100);
- })
- }).then(function(data){
- console.log(data);
- })
- <<<
- 100
其次这个返回的 promise 里也可像上面的说过的一样在这个 promise 里的 resolve 里继续嵌套 promise
- p.then(function(data){
- return new Promise(function(resolve,reject){
- resolve(new Promise(resolve,reject){
- resolve(200);
- });
- })
- }).then(function(data){
- console.log(data);
- })
- <<<
- 200
实现:
这大概是 Promise 实现最难的一部分, 主要是通过一个 resolvePromise 的方法来支持我们上述的功能, 先上完整代码
- function resolvePromise(p2, x, resolve, reject) {
- // 注意: 有可能解析的是一个第三方 promise
- if (p2 === x) { // 防止循环引用
- return reject(new TypeError('Error: 循环引用')); // 让 promise2 失败
- }
- let called; // 防止第三方的 promise 出现可能成功和失败的回调都调用的情况
- if (x !== null || (typeof x === 'object') || typeof x === 'function') {
- // 进来了只能说明可能是 promise
- try {
- let then = x.then;
- if (typeof then === 'function') {
- then.call(x,
- function(y) {
- if (called) return; // 这里可能交由的是第三方 promise 来处理, 故可能被调用两次
- called = true;
- // p.then(function(){return new Promise(){resolve(new Promise...)}}) return 中的 promise 的 resolve 又是一个 promise, 即 y 又可能是一个 promise
- resolvePromise(p2, y, resolve, reject);
- },
- function(err) {
- if (called) return;
- called = true;
- reject(err);
- });
- } else {
- resolve(x); // 可能只是组键值对, 像这样 {then:1}
- }
- } catch(e) {
- // Object.define({},'then',{value:function(){throw Error()}})
- if (called) return; // 防止第三方 promise 失败时 两个回调都执行 导致触发两次 reject 注册的回调
- called = true;
- reject(e);
- }
- } else { // 说明是个普通值 让 p2 成功
- resolve(x);
- }
- }
其中尤其要重视的是这一部分
- if(typeof then === 'function'){
- then.call(x,function(y){
- resolvePromise(p2,y,resolve,reject);
- },function(err){
- if(called) return;
- called = true;
- reject(err);
- });
- }
这里, 此时 then 中成功回调的这个 y 参数即有可能是我们所说的 resolve 里嵌套 promise 的情况,
故我们需要将这个 y 传入 resolvePromise 方法再次进行解析, 这样不断递归, 直到这个 y 变成一个普通值, 我们以这个普通值来 resolve 我们 then 中返回的 promise
防止循环引用
如果我们在一个 then 中 return 了一个 promise, 且这个 promise 还恰巧是 then 后返回的 promise 本身, 那么这个 then 返回的 promise 永远不可能会转换状态 So 为了防止出现这种情况我们会直接 reject it
如果 then 中返回的 promise 是它本身就 reject it
- let p = new Promise(function(resolve,reject){
- resolve();
- });
- var p2 = p.then(function(){
- return p2; //<--- 看这里!!
- });
- p2.then(function(){
- },function(e){
- console.log(e); // 会走这里
- });
实现:
主要是 resolvePromise 方法中的这么一句
- if(p2 === x){ // 防止循环引用
- return reject(new TypeError('Error: 循环引用')); // 让 promise2 失败
- }
延迟设计
在 promise 的设计中, 即使 promise 里没有异步 resolve, 通过 promise then 注册的回调也只会在标准的同步代码执行完成后才会执行
- let p = new Promise(function(resolve,reject){
- resolve(222);
- });
- p.then(function(data){
- console.log(data);
- })
- console.log(111);
- <<<
- 111
- 222
我们可以在代码中这样实现
- ...
- promise2 = new Promise(function(resolve,reject){
- // 因为返回值可能是普通值也可能是一个 promise, 其次也可能是别人的 promise, 故我们将它命名为 x,
- setTimeout(function(){
- try{
- let x = onFulfilled(self.value);
- resolvePromise(promise2,x,resolve,reject);
- }catch(e) {
- reject(e);
- }
- });
- });
- ...
即让 then 回调执行之前套上一层 setTimeout , 让它在下一轮执行 (但实际上原生的 promise 实现是将其作为微任务而不是宏任务执行的)
初衷猜想
这么设计很重要的一个原因在我看来是因为执行 then 回调时, 我们会调用 resolvePromise 这个方法, 而调用这个方法时我们需要将 then 新返回的 promise 传入, 但我们能在一个 new 的过程中拿到自己吗?
像这样
- let a = new A(){
- console.log(a);
- }
这样显然拿到的是 undefined
So, 为了拿到这个新返回的 promise, 故我们在外面套了一层 setTimeout 这样的东东
关于异常捕获
一个 then, 只要它有 return, 那么下一个 then 就会走成功的回调, 即使这个 return 的是一个 promise 且这个 promise 的状态为失败态
- p.then(function(data){
- return new Promise(function(resolve,reject){
- reject('失败');
- })
- })
- .then(function(data){
- console.log('会走成功');
- },function(err){
- })
- <<<
会走这里
只有一种情况下一个 then 会走失败的回调, 那就是此次的回调执行是抛出了异常,
- p.then(function(data){
- throw Error('出现错误!')
- })
- .then(function(data){
- console.log('会走成功');
- },function(err){
- console.log(err)
- })
- <<<
出现错误!
实现: 我们在两个地方使用过 try catch
一个是 executor 执行的时候
- try{ // 正因为 executor 执行是同步的, 故我们能使用 try catch
- executor(resolve,reject);
- }catch(e){
- reject(e);
- }
一个是我们 then 所注册的回调执行的时候
- ...
- promise2 = new Promise(function(resolve,reject){
- setTimeout(function(){
- try{
- let x = onFulfilled(self.value);
- resolvePromise(promise2,x,resolve,reject);
- }catch(e) {
- reject(e);
- }
- });
- });
- ...
值的穿透
promise 允许我们使用空 then(即使这样做并没有什么意义), 而这些省略了回调的 then 原本改接收的参数会被向下传递直到遇到一个不是空的 then
- var p = new Promise(function(resolve,reject){
- resolve(100)
- });
- p.then().then().then(function(data){
- console.log(data);
- },function(err){
- console.log(err)
- });
- <<<
- 100 // 也允许异常向下传递
实现:
- Promise.prototype.then = function(onFulfilled,onRejected){
- // 成功和失败默认不传, 则让他们穿透直到有传值的 then
- onFulfilled = typeof(onFulfilled)==='function'?onFulfilled:function(value){
- return value;
- };
- onRejected = typeof(onRejected)==='function'?onRejected:function(err){
- throw err;
- };
- ...
来源: https://www.thinksaas.cn/group/topic/839155/