分析一下实现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);
})
控制台能打印出我们想要的结果,说明到目前为止,我们写的都没问题,但是接下来我们思考,如果我们的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的解析,我们就到这了,接下来我们会对他的静态方法再做具体的分析
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!