如果自己实现一个 Promise,那真是太酷啦

Promise

Promise 作为前端应该都很熟悉, 话不多说,想看规范的直接点击这里 👉 PromiseA+Plus规范

编码

同步

先看原版 Promise 的同步使用

let p1 = new Promise((resolve,reject)=>{
  // resolve(100)
  console.log(123)
  reject(100)
})

p1.then(res=>{
  console.log(res,"success")
},err=>{
  console.log(err,"fail")
})
// 输出: 123  100,fail

可以看到,Promise 接收一个回调,回调函数中接收两个函数 resolvereject回调会默认执行一次,并且返回一个含有 then 属性的对象 p1,同样的 then 接收两个函数作为回调

Promise 的两个回调和 then 中的回调是有对应关系的

resolve 执行的时候,会把 resolve 中的实参会传递给 then 第一个回调函数
reject 执行的时候,会把 reject 中的实参会传递给 then 第二个回调函数

根据以上的信息,我们写出第一版 Promise

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class Promise {
  constructor(executor) {
    // 保存 resolve 中的参数
    this.value = undefined;
    
    // 保存 reject 中的参数
    this.reason = undefined;
    this.status = PENDING;

    const onFulfilled =  (val)=> {
     // 防止重复执行
      if (this.status == PENGING) {
        this.value = val;
        this.status = FULFILLED;
      }
    };
    const onRejected =  (reason)=> {
       // 防止重复执行
      if (this.status == PENDING) {
        this.reason = reason;
        this.status = REJECTED;
      }
    };

    // 默认执行一次
    executor(onFulfilled, onRejected);
  }
  then(onFulfilled,onRejected) {
    if(this.status == FULFILLED){
      onFulfilled(this.value)
    }
    if(this.status == REJECTED){
      onRejected(this.reason)
    }
  }
}

在同步状态下的 resolve/reject,我们的 Promise 可以很好的运行,因为只要执行 resolve/reject,this.status就会修改,then 中就会得到正确的 「状态

但是异步情况下,then 不会获得 最新 的 「状态」 就会有点问题

let p1 =  new Promise((resolve,reject)=>{
  setTimeout(()=>{
    reject(100)
  },1000)
})

p1.then(res=>{
  console.log(res)
},err=>{
  console.log(err)
})

此时由于是异步,内部状态 this.status 还是 PENGING, 所以 then 方法中的回调不能执行,我们需要改造一下

异步

一般情况下,我们可以使用 发布订阅模式 来解决异步问题

同样的,我们可以使用 发布订阅 来解决当前问题,在执行 then 的时候,并且是 statuspending时,进行「订阅」相应的事件,当 异步结束之后,「触发」订阅事件

class Promise {
  constructor() {
    // ...

    this.onFulfilledAry = [];
    this.onRejectedAry = [];
    // ....

    const onFulfilled = val => {
      if (this.status == PENGING) {
        this.value = val;
        this.status = FULFILLED;
        // 发布成功事件
        this.onFulfilledAry.forEach(fn => fn());
      }
    };
    const onRejected = reason => {
      if (this.status == PENGING) {
        this.reason = reason;
        this.status = REJECTED;
        // 发布失败事件
        this.onRejectedAry.forEach(fn => fn());
      }
    };
    // 默认执行一次
    executor(onFulfilled, onRejected);
  }

  then(onFulfilled, onRejected) {
     // ....
     
     // 只有 当 status 为 pending的时候,进行订阅
    if (this.status == PENDING) {
    // 订阅成功事件
      this.onFulfilledAry.push(() => {
        onFulfilled(this.value);
      });
      // 订阅失败事件
      this.onRejectedAry.push(() => {
        onRejected(this.reason);
      });
    }
  }
}

当 执行 then 方法时,发现此时的状态 仍然是 pending,由于不知道以后会调用resolve或者是reject, 所以使用 onFulfilledAryonRejectedAry 同时收集成功回调和失败回调

有同学会担心 this.valuethis.reason 是 undefined,现在确实是 undefined,由于我们 push 的是一个函数,并不会立马执行,执行的时机是调用 resolve/reject,此时 value/reason 已经有值了

举一个例子

let arr = [];
let x = 1;

arr.push(()=>{
  console.log(x) // 2
})

setTimeout(()=>{
  x = 2;
  arr.forEach(fn=>fn())
})

此时 异步resolve 也已经可以很好的运行了,上述的使用方式也是我们最常用的方法,Promise 还有其他特性
比如 then

let p1 =  new Promise((resolve,reject)=>{
  resolve(100)
})

let p2 = p1.then(res=>{
  console.log(res)  // 100
  return 123
},err=>{
  console.log(err)
  return 10
})

p2.then(res=>{
  console.log(res,"success") // 123,success
},err=>{
  console.log(err,"fail")
})

上述的代码最多只能执行一次 then

我们需要在 then 执行完毕之后再次返回一个新的 promise 才可以保证无限调用 then 链

then 链有三种返回情况

  1. 返回一个普通值
    会执行下一个 then 链的 第一个 resolve 回调中, 如上述代码所示
  2. throw
    会执行下一个 then 链的 reject 中
  3. promise
    使用 返回的 promise 的 状态 作为下一个 then 链的状态

throw 一个错误

let p1 =  new Promise((resolve,reject)=>{
  resolve(100)
})

let p2 = p1.then(res=>{
  throw 456
  return 123
},err=>{
  console.log(err)
  return 10
})

p2.then(res=>{
  console.log(res,"success")
},err=>{
  console.log(err,"fail")  // 456 fail
})

使用 返回的 promise 的状态作为下一个then 链的状态

let p1 =  new Promise((resolve,reject)=>{
  resolve(100)
})

let p2 = p1.then(res=>{
// 返回一个 reject 状态
  return new Promise((resolve,reject)=>{
    reject(100)
  })
},err=>{
  console.log(err)
  return 10
})

p2.then(res=>{
  console.log(res,"success")
},err=>{
  console.log(err,"fail") // 100 fail
})

接下来我们解决这几个问题

then 链

现在有以下的几个问题

  • then 链可以无限长
  • throw 会进入到下一个then 链的 reject 状态
  • 判断then中的返回值,如果是普通值,则直接 resolve 返回,如果是 promise ,则以 返回的promise 的状态为最终结果

我们需要改造 then 链,首先要解决的第一个点是,then 链的问题,由于promise 状态是不可更改的, 所以我们要返回一个新的 promise,我把新的 promise 叫做promise2, 每次调用 then 链都会生成一个 新的 promise

throw 问题我们可以使用 try catch 捕获,如果有错误,直接 reject

ok, 就剩下最后一个问题了,那就是 – then 中 返回值问题

promiseA+中有这么一句话,

如果自己实现一个 Promise,那真是太酷啦

如果有人写出这样的代码,需要一个类型错误

let p1 = new Promise((resolve, reject) => {
  resolve()
}).then(()=>{
  return p1
});

p1.then(res => {
   console.log(res, "res");
  },
  err => {
    console.log(err, "fail");
    // [TypeError: Chaining cycle detected for promise #<Promise>] fail
  }
);

所以我们不仅要获取 then 中的返回值,也要 判断x 是否和 当前的 ·promise· 是否是同一个值
由于代码是从右往左执行,在 [ 右侧的表达式 ] 是无法获得 [ 左侧变量的值 ]

为了解决这个问题,不得不使用一个异步任务setTimeout

then(onFulfilled, onRejected) {
    let promise2 = new Promise((resolve, reject) => {
      if (this.status == FULFILLED) {
        try {
          let x = onFulfilled(this.value);
          setTimeout(() => {
            resolvePromise(promise2, x, resolve, reject);
          });
        } catch (err) {
          reject(err);
        }
      }
      if (this.status == REJECTED) {
        try {
          let x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        } catch (err) {
          reject(err);
        }
      }
      if (this.status == PENGING) {
        this.onFulfilledAry.push(() => {
          try {
           let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
        this.onRejectedAry.push(() => {
          let x = onRejected(this.reason);
          try {
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      }
    });
    return promise2;
  }

使用方法resolvePromise 来判断返回值类型
promiseA+ 中,是这样规定的

如果自己实现一个 Promise,那真是太酷啦

根据 promiseA+ 规范写出如下代码

function resolvePromise(promise2, x, resolve, reject) {
  if (x == promise2) return new TypeError('循环引用');
  let then, called;
   //  2.3.3 x 是函数或者对象
  if (x != null && ((typeof x == 'object' || typeof x == 'function'))) {
  // 2.3.3.2 异常直接reject
    try {
    // 2.3.3.1 then 为 x.then
      then = x.then;
      
      // 2.3.3.3 判断函数
      if (typeof then == 'function') {
      // 2.3.3.3 x 作为 this
      // 相当于 x.then(function(y){},function(r){})
        then.call(x, function (y) {
         // 2.3.3.3.4.1 已经调用过,直接忽略掉
          if (called) return;
          called = true;
          // 2.3.3.3.1 如果是 resolve,则继续往下判断是否是 promise
          resolvePromise(promise2, y, resolve, reject);
        }, function (r) {
           // 2.3.3.3.4.1 已经调用过,直接忽略掉
          if (called) return;
          called = true;
          // 2.3.3.3.2 如果是 reject ,直接停止判断
          reject(r);
        });
      } else {
      // 不是函数,只是一个普通对象
        resolve(x);
      }
    } catch (e) {
    // 2.3.3.2 异常直接reject
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
  // 普通值直接返回
    resolve(x);
  }
}

2.3.3.3.1 中 Promise 递归执行,直到 x 是一个普通值

let p1 = new Promise((resolve, reject) => {
  resolve()
}).then(()=>{
  return new Promise((resolve,reject)=>{
    resolve(new Promise((resolve)=>{
      resolve(10)
    }))
  })
});

p1.then(res=>{
  console.log(res)  // 10 
})

2.3.3.3.2,看似调用的 resolve,其实内部调用的 reject

let p1 = new Promise((resolve, reject) => {
  resolve()
}).then(()=>{
  return new Promise((resolve,reject)=>{
    resolve(new Promise((resolve)=>{
      reject(100)
    }))
  })
});

p1.then(res=>{
  console.log(res)
},err=>{
  console.log(err,"err") // 100,err
})

参数穿透

PromiseA+Plus 省略参数

如果自己实现一个 Promise,那真是太酷啦

1.忽略 resolve

let p1 = new Promise((resolve, reject) => {
  resolve(1)
});

p1.then().then().then(res=>{
  console.log(res) // 1
})

  1. 忽略 reject
let p1 = new Promise((resolve, reject) => {
  reject(1)
});

p1.then().then().then(res=>{
  console.log(res) 
},err=>{
  console.log(err) // 1
})

如果没有传递,我们需要给他一个默认

then(onFulfilled, onRejected){
    onFulfilled = typeof onFulfilled == "function" ? onFulfilled : v=>v
    onRejected = typeof onRejected == "function" ? onRejected : v => { throw v };
    //...
}

onReject 使用 throw ,否则会跳转到下一个 then 中的 resolve

完整代码

const PENGING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function resolvePromise(promise2, x, resolve, reject) {
if (x == promise2) return new TypeError('循环引用');
let then, called;
//  2.3.3 x 是函数或者对象
if (x != null && ((typeof x == 'object' || typeof x == 'function'))) {
// 2.3.3.2 异常直接reject
try {
// 2.3.3.1 then 为 x.then
then = x.then;
// 2.3.3.3 判断函数
if (typeof then == 'function') {
// 2.3.3.3 x 作为 this
// 相当于 x.then(function(y){},function(r){})
then.call(x, function (y) {
// 2.3.3.3.4.1 已经调用过,直接忽略掉
if (called) return;
called = true;
// 2.3.3.3.1 如果是 resolve,则继续往下判断是否是 promise
resolvePromise(promise2, y, resolve, reject);
}, function (r) {
// 2.3.3.3.4.1 已经调用过,直接忽略掉
if (called) return;
called = true;
// 2.3.3.3.2 如果是 reject ,直接停止判断
reject(r);
});
} else {
// 不是函数,只是一个普通对象
resolve(x);
}
} catch (e) {
// 2.3.3.2 异常直接reject
if (called) return;
called = true;
reject(e);
}
} else {
// 普通值直接返回
resolve(x);
}
}
class Promise {
constructor(executor) {
//
this.value = undefined;
this.reason = undefined;
this.status = PENGING;
this.onFulfilledAry = [];
this.onRejectedAry = [];
const onFulfilled = val => {
if (this.status == PENGING) {
this.value = val;
this.status = FULFILLED;
this.onFulfilledAry.forEach(fn => fn());
}
};
const onRejected = reason => {
if (this.status == PENGING) {
this.reason = reason;
this.status = REJECTED;
this.onRejectedAry.forEach(fn => fn());
}
};
// 默认执行一次
executor(onFulfilled, onRejected);
}
then(onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject) => {
if (this.status == FULFILLED) {
try {
let x = onFulfilled(this.value);
setTimeout(() => {
resolvePromise(promise2, x, resolve, reject);
});
} catch (err) {
reject(err);
}
}
if (this.status == REJECTED) {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}
if (this.status == PENGING) {
this.onFulfilledAry.push(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
this.onRejectedAry.push(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
}
});
return promise2;
}
}
module.exports = Promise;

总结

通过一个手写 Promise,对这个 Api 有了更深入的理解,以后无论在工作中,还是在面试中都能够使用得当,不会再有困惑。
加油!

原文链接:https://juejin.cn/post/7234447275861344293 作者:好大猫

(0)
上一篇 2023年5月19日 上午10:56
下一篇 2023年5月19日 上午11:07

相关推荐

发表回复

登录后才能评论