最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 深度解析Promise(二)

    正文概述 掘金(小雯的老公)   2021-02-06   382

    分析一下实现Promise需要哪些逻辑

    • Promise有三种状态,pending(进行中)、fulfilled(已完成)、reject(已失败),外界无法改变其状态,且一旦状态改变就不会再变了
    • 实例化一个 Promise 需要传入一个 executor 函数 ,业务代码在 executor 函数中执行,另外 executor 函数接收两个参数 resolve 和 reject。resolve 和 reject 是 Promise 构造函数的内置函数
    new Promise((resolve,reject)=>{
    	// do something
    })
    
    • 在 executor 函数中业务代码执行成功了,调用 resolve 函数,把 Promise 的状态变为已成功,另外通过参数把业务代码的执行成功的结果传递到 Promise 中
    • 在 executor 函数中业务代码执行失败了,调用 reject 函数,把 Promise 的状态变为已失败,另外通过参数把业务代码的执行失败的原因传递到 Promise 中
    • 实例方法 then 的第一个参数是业务代码执行成功的回调函数,第二个参数是业务代码执行失败的回调函数,当业务代码执行完毕后,会根据执行结果调用对应的回调函数,且这些回调函数接收业务代码的执行结果作为参数
    • then方法可以链式调用,具有穿透性
    • 实例方法 catch 来添加业务代码执行失败的回调函数

    那么下面就一一来实现 Promise 的功能

    初步搭建

    根据分析,首先我们需要完成这些功能:

    • Promise 构造函数接收 executor 函数作为参数,且在其中执行 executor 函数

    • Promise 构造函数中有 resolve 和 reject 内置方法,并作为参数传递给 executor 函数

    • 设置个实例属性 status 来存储状态

    • 内置函数 resolve 可以把状态变为已成功,内置函数 reject 可以把状态变为已失败,且一旦状态改变就不会再变

    // 这里使用了Symbol是为了防止外界其他原因改变了状态
    const Pending = Symbol('Pending');
    const Fulfilled = Symbol('Fulfilled');
    const Rejected= Symbol('Rejected');
    
    function Promise(excutor){
    	// 初始状态为pending
    	this.status = Pending;
    	const resolve = () => {  // 这里为了this的指向,使用了箭头函数,如果不用箭头函数,需要用变量存一下this
    		if(this.status === Pending){  // 只有在状态为pending时才可以发生改变
    			this.status = Fulfilled;
    		}
    	};
    	const reject = () => {
    		if(this.status === Pending){  // 只有在状态为pending时才可以发生改变
    			this.status = Rejected;
    		}
    	};
    	excutor(resolve,reject);
    }
    

    初步实现then方法

    按照上面对 then 实例方法的业务场景的简单分析,在 then 实例方法中调用回调函数时,还要把 executor 函数中业务代码的执行结果作为参数传递进去,那么要新增实例属性来存储业务代码的执行结果。另外执行成功的结果通过内置方法 resolve 的参数传入,其执行失败的原因通过内置方法 reject 的参数传入

    因为then方法是实例可调用的,所以then方法是在构造函数原型上的:

    // 这里使用了Symbol是为了防止外界其他原因改变了状态
    const Pending = Symbol('Pending');
    const Fulfilled = Symbol('Fulfilled');
    const Rejected= Symbol('Rejected');
    
    function Promise(excutor){
    	// 初始状态为pending
    	this.status = Pending;
    	this.value = undefined;
    	this.error = undefined;
    	const resolve = value => {  // 这里为了this的指向,使用了箭头函数,如果不用箭头函数,需要用变量存一下this
    		if(this.status === Pending){  // 只有在状态为pending时才可以发生改变
    			this.status = Fulfilled;
    			this.value = value;
    		}
    	};
    	const reject = value => {
    		if(this.status === Pending){  // 只有在状态为pending时才可以发生改变
    			this.status = Rejected;
    			this.error = value;
    		}
    	};
    	excutor(resolve,reject);
    };
    
    Promise.prototype.then = function(resolveCallback,rejectCallback){  // 这里为什么不用箭头函数,注意this指向
    	if(this.status === Fulfilled){
    		if(resolveCallback && typeof resolveCallback === 'function'){
    			resolveCallback(this.value);
    		}
    	}
    	if(this.status === Rejected){
    		if(rejectCallback && typeof rejectCallback === 'function'){
    			rejectCallback(this.error);
    		}
    	}
    }
    

    初步实现了then方法,那么我来验证一下:

    const promise = new Promise((resolve,reject)=>{
    	resolve('执行成功');
    });
    
    promise.then(res=>{
    	console.log(res);
    })
    

    深度解析Promise(二) 控制台能打印出我们想要的结果,说明到目前为止,我们写的都没问题,但是接下来我们思考,如果我们的excutor是个执行异步方法的函数呢:

    const promise = new Promise((resolve,reject)=>{
    	setTimeout(()=>{
    		resolve('执行成功');
    	},2000);
    });
    
    promise.then(res=>{
    	console.log(res);
    })
    

    结果可以发现,2秒后并没有打印出我们想要的结果,因为调用 then 实例方法时,Promise 的状态是 Pending ,虽然2秒后 Promise 的状态变为 Fulfilled ,但是 then 实例方法已经调用过了

    那么要怎么控制 then 实例方法中回调函数的执行时机。可以用发布订阅者的设计模式来实现。

    当调用 then 实例方法时,如果 Promise 的状态是 Pending 时,先将成功回调函数和失败回调函数分别存放起来,在 executor 函数中异步任务执行结束,触发内置方法 resolve 或 reject,在其中去依次调用这些回调函数。

    const Pending = Symbol('Pending');
    const Fulfilled = Symbol('Fulfilled');
    const Rejected= Symbol('Rejected');
    
    function Promise(excutor){
    	this.status = Pending;
    	this.value = undefined;
    	this.error = undefined;
    	this.onFulfilled = [];  //这里用数组来存放resolve时的回调
    	this.onRejected = []; //这里用数组来存放reject时的回调
    	const resolve = value => {
    		if(this.status === Pending){
    			this.status = Fulfilled;
    			this.value = value;
    			this.onFulfilled.forEach(fn => fn());
    		}
    	};
    	const reject = value => {
    		if(this.status === Pending){
    			this.status = Rejected;
    			this.error = value;
    			this.onRejected.forEach(fn => fn());
    		}
    	};
    	excutor(resolve,reject);
    };
    
    Promise.prototype.then = function(resolveCallback,rejectCallback){
    	if(this.status === Fulfilled){
    		if(resolveCallback && typeof resolveCallback === 'function'){
    			resolveCallback(this.value);
    		}
    	}
    	if(this.status === Rejected){
    		if(rejectCallback && typeof rejectCallback === 'function'){
    			rejectCallback(this.error);
    		}
    	}
    	//如果调用then方法的时候,状态还是pending,就先将回调保存起来
    	if(this.status === Pending){
    		if(resolveCallback && typeof resolveCallback === 'function'){
    			this.onFulfilled.push(()=>{
    				resolveCallback(this.value);
    			});
    		}
    		if(rejectCallback && typeof rejectCallback === 'function'){
    			this.onRejected.push(()=>{
    				rejectCallback(this.error)
    			});
    		}
    	}
    }
    

    再进行实例化验证一下:

    const promise = new Promise((resolve,reject)=>{
    	setTimeout(()=>{
    		resolve('执行成功');
    	},2000);
    });
    
    promise.then(res=>{
    	console.log(res);
    })
    

    结果控制台在2秒后能够成功打印出“执行成功”,说明目前我们的逻辑都正确,再测试一下reject的情况:

    const promise = new Promise((resolve,reject)=>{
    	setTimeout(()=>{
    		reject('执行失败');
    	},2000);
    });
    
    promise.then(res=>{
    	// do nothing
    },err=>{
    	console.log(err)
    })
    

    控制台在2秒后能够成功打印出“执行失败”

    then方法的微任务机制

    由于原生的 Promise 是V8引擎提供的微任务,我们无法还原V8引擎的实现,所以这里使用 setTimeout 模拟异步,所以原生的是微任务,这里是宏任务代替(如果你想实现 promise 的微任务,可以 mutationObserver 替代 seiTimeout 来实现微任务。这里只是模拟异步而已)

    Promise.prototype.then = function(resolveCallback,rejectCallback){  
    	if(this.status === Fulfilled){
    		if(resolveCallback && typeof resolveCallback === 'function'){
    			// 用setTimeout代替微任务
    			setTimeout(()=>{
    				resolveCallback(this.value);
    			},0);
    		}
    	}
    	if(this.status === Rejected){
    		if(rejectCallback && typeof rejectCallback === 'function'){
    			setTimeout(()=>{
    				rejectCallback(this.error);
    			},0);
    		}
    	}
    	if(this.status === Pending){
    		if(resolveCallback && typeof resolveCallback === 'function'){
    			this.onFulfilled.push(()=>{
    				setTimeout(()=>{
    					resolveCallback(this.value);
    				},0);
    			});
    		}
    		if(rejectCallback && typeof rejectCallback === 'function'){
    			this.onRejected.push(()=>{
    				setTimeout(()=>{
    					rejectCallback(this.error);
    				},0);
    			});
    		}
    	}
    }
    

    then的链式调用

    实例方法 then 链式调用有两个要求:

    • 在实例方法 then 后面可以直接使用实例方法 then
    • 在前面一个实例方法 then 返回一个值,不管是什么值,在后面一个实例方法 then 中都能获取到

    既然要能链式调用,那么then中返回的肯定也是一个promise,把实例方法 then 返回的值 value,通过 resolve(value) 或 reject(value) 传递出去

    当然我们还需要一个专门处理then的函数,用来解决对实例方法then返回的值的类型做判断和对应处理:

    Promise.prototype.then = function(resolveCallback,rejectCallback){
    	let promise = new Promise((resolve,reject)=>{
    		if(this.status === Fulfilled){
    			if(resolveCallback && typeof resolveCallback === 'function'){
    				setTimeout(()=>{
    					let x = resolveCallback(this.value);
    					handleValue(promise,x,resolve,reject);   // 这里用工具函数处理
    				},0);
    			}
    		}
    		if(this.status === Rejected){
    			if(rejectCallback && typeof rejectCallback === 'function'){
    				setTimeout(()=>{
    					let x = rejectCallback(this.error);
    					handleValue(promise,x,resolve,reject);
    				},0);
    			}
    		}
    		if(this.status === Pending){
    			if(resolveCallback && typeof resolveCallback === 'function'){
    				this.onFulfilled.push(()=>{
    					setTimeout(()=>{
    						let x = resolveCallback(this.value);
    						handleValue(promise,x,resolve,reject);
    					},0);
    				});
    			}
    			if(rejectCallback && typeof rejectCallback === 'function'){
    				this.onRejected.push(()=>{
    					setTimeout(()=>{
    						let x = rejectCallback(this.error);
    						handleValue(promise,x,resolve,reject);
    					},0);
    				});
    			}
    		}
    	})
    	return promise
    }
    

    然后写一下这个handleValue工具函数:

    const handleValue = (promise,x,resolve,reject)=>{
    	// 如果自己循环调用自己
    	if(promise === x){
    		return reject(new TypeError('链式循环调用了'));
    	}
    	let once = false;   // 确保只传递出去一次值
    	if(typeof x === 'object' && x !== null || typeof x === 'function'){
    		const then = x.then;
    		// 判断x是不是Promise
    		if(then && typeof then === 'function'){
    			//调用then实例方法处理Promise执行结果
    			then.call(x,y=>{
    				if(once) return
    				once = true;  // 防止Promise中Promise执行成功后又传递一个Promise过来,
    				handleValue(promise,y,resolve,reject);   // 递归解析
    			},r=>{
    				if(once) return
    				once = true;
    				reject(r);
    			})
    		}else{
    			// 如果x是个普通对象,直接调用resolve(x)
    			resolve(x);
    		}
    	}else{
    		// 如果x是个原始值,直接调用resolve(x)
    		resolve(x);
    	}
    }
    

    在上述代码中,判断typeof then === 'function'时其实是在判断返回的 x 是否为一个 Promise。如果没有 then 函数,x 即为普通值,直接返回 resolve(x)。如果有 then 函数,x 即为一个 Promise,就递归解析这个 Promise,直到 x 是一个普通值后作为最后的结果返回

    那么为什么用typeof then === 'function' 判断 x 是否为一个 Promise ,而不是用 x instanceof Promise 。 这是为了让 Promise 更具有通用性,所以一个 thenable 对象也可以看做是一个 Promise 。 thenable 对象就是一个拥有 then 方法的对象,如下代码所示例:

    let thenable = {
        then: function(resolve, reject){
        	resolve('执行成功')
        }
    }
    

    在 thenable.then 方法中通过 resolve 传递执行成功的结果。但是 thenable 对象不是通过 Promise 类 new 出来的,故不能通过 x instanceof Promise 来判断是不是一个 Promise

    then的穿透

    如果要实现如下的逻辑呢?

    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('"执行成功"')
        }, 2000)
    
    })
    p.then().then(res =>{
        console.log(res)
    })
    

    此时的then方法是需要具备穿透性的,后面的实例方法 then 依旧可以得到之前实例方法 then 返回的值,我们再来修改一下:

    Promise.prototype.then = function(resolveCallback,rejectCallback){
    	resolveCallback = typeof resolveCallback === 'function' ? resolveCallback : v => v;
    	rejectCallback = typeof rejectCallback ===  'function'  ? rejectCallback : err => {
    		throw err
    	};
    	let promise = new Promise((resolve,reject)=>{
    		if(this.status === Fulfilled){
    			if(resolveCallback && typeof resolveCallback === 'function'){
    				setTimeout(()=>{
    					let x = resolveCallback(this.value);
    					handleValue(promise,x,resolve,reject); 
    				},0);
    			}
    		}
    		if(this.status === Rejected){
    			if(rejectCallback && typeof rejectCallback === 'function'){
    				setTimeout(()=>{
    					let x = rejectCallback(this.error);
    					handleValue(promise,x,resolve,reject);
    				},0);
    			}
    		}
    		if(this.status === Pending){
    			if(resolveCallback && typeof resolveCallback === 'function'){
    				this.onFulfilled.push(()=>{
    					setTimeout(()=>{
    						let x = resolveCallback(this.value);
    						handleValue(promise,x,resolve,reject);
    					},0);
    				});
    			}
    			if(rejectCallback && typeof rejectCallback === 'function'){
    				this.onRejected.push(()=>{
    					setTimeout(()=>{
    						let x = rejectCallback(this.error);
    						handleValue(promise,x,resolve,reject);
    					},0);
    				});
    			}
    		}
    	})
    	return promise
    }
    

    catch方法

    catch其实就是 then(null, rejectCallback)的别名

    Promise.prototype.catch =  function(rejectCallback){
    	this.then(null,rejectCallback);
    }
    

    优化完整代码

    处理一下内部错误等

    const Fulfilled = Symbol('Fulfilled')
    const Rejected = Symbol('Rejected')
    const Pending = Symbol('Pending')
    
    const handleValue = (promise,x,resolve,reject) => {
      if(promise === x){
        return reject(new TypeError('检测到循环调用'))
      }
      let once = false
      if(typeof x === 'object' && x !== null || typeof x === 'function'){
        const then = x.then
        if(typeof then === 'function'){
          then.call(x,y=>{
            if(once) return
            once = true
            handleValue(promise,y,resolve,reject)
          },r=>{
            if(once) return
            once = true
            reject(r)
          })
        }else{
          resolve(x)
        }
      }else{
        resolve(x)
      }
    }
    
    function Promise(excutor){
      this.status = Pending
      this.value = undefined
      this.reason = undefined
      this.onFulfilled = []
      this.onRejected = []
    
      const resolve = value => {
        if(this.status === Pending){
          this.status = Fulfilled
          this.value = value
          this.onFulfilled.forEach(fn => {
            if(typeof fn === 'function'){
              fn()
            }
          })
        }
      }
    
      const reject = value => {
        if(this.status === Pending){
          this.status = Rejected
          this.reason = value
          this.onRejected.forEach(fn => {
            if(typeof fn === 'function'){
              fn()
            }
          })
        }
      }
    
      try{
        excutor(resolve,reject)
      }
      catch(err){
        reject(err)
      }
    }
    
    Promise.prototype.then = function(fulfilledCallback,rejectedCallback){
      fulfilledCallback = typeof fulfilledCallback === 'function' ? fulfilledCallback : v => v
      rejectedCallback = typeof rejectedCallback === 'function' ? rejectedCallback : err => {
        throw err
      }
      let promise = new Promise((resolve,reject)=>{
        if(this.status === Pending){
          if(fulfilledCallback && typeof fulfilledCallback === 'function'){
            this.onFulfilled.push(()=>{
              setTimeout(()=>{
                try{
                  let x = fulfilledCallback(this.value)
                  handleValue(promise,x,resolve,reject)
                }catch(err){
                  reject(err)
                }
              },0)
            })
          }
          if(rejectedCallback && typeof rejectedCallback === 'function'){
            this.onRejected.push(()=>{
              setTimeout(()=>{
                try{
                  let x = rejectedCallback(this.reason)
                  handleValue(promise,x,resolve,reject)
                }catch(err){
                  reject(err)
                }
              })
            })
          }
        }
    
        if(this.status === Fulfilled){
          if(fulfilledCallback && typeof fulfilledCallback === 'function'){
            setTimeout(()=>{
              try{
                let x = fulfilledCallback(this.value)
                handleValue(promise,x,resolve,reject)
              }catch(err){
                reject(err)
              }
            },0)
          }
        }
    
        if(this.status === Rejected){
          if(rejectedCallback && typeof rejectedCallback === 'function'){
            setTimeout(()=>{
              try{
                let x = rejectedCallback(this.reason)
                handleValue(promise,x,resolve,reject)
              }catch(err){
                reject(err)
              }
            })
          }
        }
      }) 
      return promise
    }
    
    Promise.prototype.catch = function(rejectCallback){
      this.then(null,rejectCallback)
    }
    

    那么对Promise和他的实例方法then和catch的解析,我们就到这了,接下来我们会对他的静态方法再做具体的分析


    下载网 » 深度解析Promise(二)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元