带你手写 Promise,别再死记硬背了

一、动机

Promise 是目前处理异步操作中必不可少的内容,即使你使用了 async await ,但最后得到的还是个被 Promise 包裹的内容,例如:

因此,无论是在实际学习、工作或者面试中,都无法避免 Promise 的相关使用和原理,特别是在面试中甚至会让你自己实现一个 Promise. 本文主要介绍 Promise 的一些使用和特性,并使用自己的代码去实现它对应的功能,当然注重的应该是对实现过程的理解,而不是死记硬背!!!

二、Promise 前置知识

要想要深刻了解某个知识,前提条件是:你可以不是很明白其中原理,但是你必须知道如何正常使用?因此,建议对不了解的知识,首先得学会基本使用,然后在从使用层面去接触底层原理。

基本介绍
  • Promise 实例拥有三个状态:【pending、fulfilled、rejected】. 并且这三种状态只能从 【pending --> fulfilled】 或 【pending --> rejected】, 也就是说只要当前状态不是 pending ,那么当前状态就不能被改变.
  • 1. 实例化 Promise 时,可以接收一个函数作为参数,这个函数可以接收到 resolvereject 两个实例方法,用于更改当前实例的状态,并把它们接收到的参数传递给下一个 then 对应的参数中.* PS: Promise 中的回调是被同步执行的,但是 then 操作属于异步执行
  • 2. then 方法可以接收两个函数作为参数,第一个函数相当于执行上一个 promise 中执行 resolve 后对应的回调,第二个函数相当于执行上一个 promise 中执行 reject 后对应的回调.
 // 1. 实例化 Promise 时
// 可以接收一个函数作为参数,这个函数可以接收到 resolve 和 reject 两个实例方法
// 用于更改当前实例的状态,并把它们接收到的参数传递给下一个 then 对应的参数中new Promise((resolve, reject) => {// resolve 和 reject 可以都执行,但都执行的意义不大,因为 promise 状态发生更改后,就不能在被更改resolve('ok');// reject('err');}).then((value) => {console.log("resolve callback = ", value); // 若执行 resolve,则 value = ok}, (reason) => {console.log("reject callback = ", reason); // 若执行 reject,则 value = err});// 2. then 方法可以接收两个函数作为参数:// 第一个函数相当于执行上一个 promise 中执行 resolve 后对应的回调// 第二个函数相当于执行上一个 promise 中执行 reject 后对应的回调 
  • 3.then 的两个回调函数中返回的值,默认可以被下一个 then 中的第一个回调函数接收到,其实表明了,then 操作之后会得到一个新的 promise,并且这个新的 promise 的状态默认 fulfilled.
// 3. 在 then 的两个回调函数中返回的值,默认可以被下一个 then 中的第一个回调函数接收到
 // 其实表明了,then 操作之后会得到一个新的 promise,并且这个新的 promise 的状态默认 fulfilled
 let p1 = new Promise((resolve, reject) => {resolve('ok');// reject('err');}); let p2 = p1.then((value) => {console.log("resolve p1.then = ", value); // okreturn 'then ok';}, (reason) => {console.log("reject p1.then = ", reason);// errreturn 'then err';})p2.then((value) => {console.log("resolve p2.then = ", value);// ok || err}, (reason) => {console.log("reject p2.then = ", reason);}); 
  • 4. then 中允许返回 Promise 实例,但不允许返回同一个 Promise 实例. 换句话说,不能在同一个 Promise 实例的 then 操作中返回它自己.
  • 5. then 方法中只要设置了上一个 Promisereject 对应的回调,那么就可以正常接收到错误信息,否则按 JavaScript报错机制显示.
 // 4. then 中允许返回 Promise 实例,但不允许返回同一个 Promise 实例 // 换句话说,不能在同一个 Promise 实例的 then 操作中返回它自己 let p1 = new Promise((resolve, reject) => {resolve('ok');// reject('err');}); let p2 = p1.then((value) => {console.log("resolve p1.then = ", value); // okreturn p2;}, (reason) => {console.log("reject p1.then = ", reason);// errreturn p2;})p2.then((value) => {console.log("resolve p2.then = ", value);// ok || err}, (reason) => {console.log("reject p2.then = ", reason);// TypeError: Chaining cycle detected for promise #<Promise>});// 5. then 方法中只要设置了上一个 Promise 中 reject 对应的回调,那么就可以正常接收到错误信息,否则按 JavaScript 的报错机制显示. 
  • 6. Promise.all() 以数组形式接收多个 Promise ,当所有的 Promise 执行完成,且状态都为 fulfilled 时,将他们的执行结果以数组形式返回给下一个 then 操作中的 第一个函数回调,否则将发生错误的 Promise 结果,返回第二个函数回调.
 // 1. p1 p2 都成功let p1 = new Promise((resolve, reject)=>{setTimeout(()=>{resolve('p1 success');},1000);});let p2 = new Promise((resolve, reject)=>{setTimeout(()=>{resolve('p2 success');// reject('p2 fail');},2000);});Promise.all([p1,p2]).then(value => {console.log('all promise success = ', value); // all promise success =(2) ["p1 success", "p2 success"]},reason => {console.log('one promise fail = ', reason);});// 2. p1 成功 p2 失败let p1 = new Promise((resolve, reject)=>{setTimeout(()=>{resolve('p1 success');},1000);});let p2 = new Promise((resolve, reject)=>{setTimeout(()=>{// resolve('p2 success');reject('p2 fail');},2000);});Promise.all([p1,p2]).then(value => {console.log('all promise success = ', value);},reason => {console.log('one promise fail = ', reason);// one promise fail =p2 fail}); 
  • 7. Promise.race() 以数组形式接收多个 Promise ,只要有一个 Promise 先执行完成,无论什么状态,都以这个结果返回给下一个 then 中对应的回调函数.
 let p1 = new Promise((resolve, reject)=>{setTimeout(()=>{resolve('p1 success');// reject('p1 fail');},1000);});let p2 = new Promise((resolve, reject)=>{setTimeout(()=>{resolve('p2 success');},2000);});Promise.race([p1,p2]).then(value => {console.log('success = ', value);// success =p1 success},reason => {console.log('fail = ', reason);}); 

三、实现 MyPromise

1. 实现基本功能

  • new 操作
  • resovle & reject 方法
  • then 方法
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// promise 接收⼀个函数参数,该函数会⽴即执⾏
class MyPromise {constructor(fn) {this.value;this.status = PENDING;// 默认状态// 这里使用 try catch 捕获中可能发生的错误try {// 这里必须要绑定 this,否则在外部调用时 this 就不会执行当前实例fn(this.resolve.bind(this), this.reject.bind(this));} catch (error) {this.reject.bind(this, error)}}resolve(value) {if (this.status === PENDING) {this.value = value;this.status = RESOLVED;}}reject(reason) {if (this.status === PENDING) {this.value = reason;this.status = REJECTED;}}
}

MyPromise.prototype.then = function (onResolve, onReject) {// 上一个 promise 实例调用了 resolveif(this.status === RESOLVED){onResolve(this.value);}// 上一个 promise 实例调用了 rejectif(this.status === REJECTED){onResolve(this.value);}

} 

2. 考虑异步改变状态的情况

  • 暴露问题:虽然在【1】 中我们实现了最最最基本的功能,但是如果我们更改状态的方式是异步的,那么会出现什么问题呢?* 下面的程序会输出什么呢?答案是啥也不会输出,因为状态是通过 setTimeout 异步修改的,而 then 中的代码全是同步操作,因此,在 setTimeout 中的 resolve 执行之前,then 就已经执行了,而此时 promise 的状态还是 pending,而 panding 状态我们没有处理.
 // 可以思考一下,这样的操作 MyPromise 会有什么输出? let p = new MyPromise((resolve, reject) => {setTimeout(()=>{resolve(1);},1000);}).then(value => {console.log('then resolve = ', value);}, reason => {console.log('then reject = ', reason);}); 
  • 解决问题:缓存回调,等待执行.
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// promise 接收⼀个函数参数,该函数会⽴即执⾏
class MyPromise {constructor(fn) {this.value;this.status = PENDING;// 默认状态this.onResolveCallBack = [];// 缓存 onResolve this.onRejectCallBack = [];// 缓存 onReject // 这里使用 try catch 捕获中可能发生的错误try {// 这里必须要绑定 this,否则在外部调用时 this 就不会执行当前实例fn(this.resolve.bind(this), this.reject.bind(this));} catch (error) {this.reject.bind(this, error);}}resolve(value) {if (this.status === PENDING) {this.value = value;this.status = RESOLVED;// 遍历调用 onResolveCallBackthis.onResolveCallBack.forEach(r => r());}}reject(reason) {if (this.status === PENDING) {this.value = reason;this.status = REJECTED;// 遍历调用 onRejectCallBackthis.onRejectCallBack.forEach(r => r());}}

}

MyPromise.prototype.then = function (onResolve, onReject) {// 当前 promise 实例调用了 resolveif (this.status === RESOLVED) {onResolve(this.value);}// 当前 promise 实例调用了 rejectif (this.status === REJECTED) {onReject(this.value);}// 当前 promise 状态为 pending,把当前的 onResolve & onReject 缓存起来if (this.status === PENDING) {this.onResolveCallBack.push(() => {onResolve(this.value);});this.onRejectCallBack.push(() => {onReject(this.value);});}

} 

3. 实现 then 链式调用 & then 穿透 & 异步操作 & 错误捕获

  • PS:then 穿透指当某个 then 操作中没有对应的回调处理,把当前 promise 的值透传给一下个 then. 例如:
  • 问题暴露:* 未实现多个 then 间的链式调用* 未实现 then 穿透* 未实现 resolve & reject 异步* 未捕获所有回调函数可能出现的错误,并传递给下一个 then 的 onReject 回调
  • 解决问题:* then 中返回一个新的 promise* 对 then 中接收的参数做兼容处理* 调用回调时,通过 setTimeout 包裹处理* 通过 try catch 实现错误捕获,然后调用 promise 中的 reject
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// promise 接收⼀个函数参数,该函数会⽴即执⾏
class MyPromise {constructor(fn) {this.value;this.status = PENDING;// 默认状态this.onResolveCallBack = [];// 缓存 onResolve this.onRejectCallBack = [];// 缓存 onReject // 这里使用 try catch 捕获中可能发生的错误try {// 这里必须要绑定 this,否则在外部调用时 this 就不会执行当前实例fn(this.resolve.bind(this), this.reject.bind(this));} catch (error) {this.reject.bind(this, error);}}resolve(value) {if (this.status === PENDING) {this.value = value;this.status = RESOLVED;// setTimeout 为了保证异步顺序执行setTimeout(() => {// 遍历调用 onResolveCallBackthis.onResolveCallBack.forEach(r => r());});}}reject(reason) {if (this.status === PENDING) {this.value = reason;this.status = REJECTED;// setTimeout 为了保证异步顺序执行setTimeout(() => {// 遍历调用 onRejectCallBackthis.onRejectCallBack.forEach(r => r());});}}

}

MyPromise.prototype.then = function (onResolve, onReject) {// 保证 onResolve & onReject 为函数// 主要是为了 .then().then((v)=>v) 的情况,称之为 then 穿透onResolve = typeof onResolve === 'function' ? onResolve : (v) => v;onReject = typeof onReject === 'function' ? onReject : (v) => v;// 这里是为了实现链式操作return new MyPromise((resovle, reject) => {// 当前 promise 实例调用了 resolveif (this.status === RESOLVED) {// setTimeout 为了保证异步顺序执行setTimeout(() => {try {let result = onResolve(this.value);resovle(result); // 下一个 promise 的状态为 fulfilled } catch (error) {reject(error);}});}// 当前 promise 实例调用了 rejectif (this.status === REJECTED) {// setTimeout 为了保证异步顺序执行setTimeout(() => {try {let result = onReject(this.value);resovle(result); // 下一个 promise 的状态为 fulfilled} catch (error) {reject(error);}});}// 当前 promise 状态为 pending,把当前的 onResolve & onReject 缓存起来if (this.status === PENDING) {this.onResolveCallBack.push(() => {try {let result = onResolve(this.value);resovle(result); // 下一个 promise 的状态为 fulfilled } catch (error) {reject(error);}});this.onRejectCallBack.push(() => {try {let result = onReject(this.value);resovle(result); // 下一个 promise 的状态为 fulfilled} catch (error) {reject(error);}});}});
} 

5. then 中的逻辑判断

  • then 手动返回 promise 实例,下一个 then 操作要依据返回的 promise 状态
  • 不允许返回当前 promise 实例本身
  • 抽取重复逻辑
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// promise 接收⼀个函数参数,该函数会⽴即执⾏
class MyPromise {constructor(fn) {this.value;this.status = PENDING;// 默认状态this.onResolveCallBack = [];// 缓存 onResolve this.onRejectCallBack = [];// 缓存 onReject // 这里使用 try catch 捕获中可能发生的错误try {// 这里必须要绑定 this,否则在外部调用时 this 就不会执行当前实例fn(this.resolve.bind(this), this.reject.bind(this));} catch (error) {this.reject.bind(this, error);}}resolve(value) {if (this.status === PENDING) {this.value = value;this.status = RESOLVED;// setTimeout 为了保证异步顺序执行setTimeout(() => {// 遍历调用 onResolveCallBackthis.onResolveCallBack.forEach(r => r());});}}reject(reason) {if (this.status === PENDING) {this.value = reason;this.status = REJECTED;// setTimeout 为了保证异步顺序执行setTimeout(() => {// 遍历调用 onRejectCallBackthis.onRejectCallBack.forEach(r => r());});}}

}

MyPromise.prototype.then = function (onResolve, onReject) {// 保证 onResolve & onReject 为函数// 主要是为了 .then().then((v)=>v) 的情况,称之为 then 穿透onResolve = typeof onResolve === 'function' ? onResolve : (v) => v;onReject = typeof onReject === 'function' ? onReject : (v) => v;// 这里是为了实现链式操作let promise = new MyPromise((resolve, reject) => {// 当前 promise 实例调用了 resolveif (this.status === RESOLVED) {// setTimeout 为了保证异步顺序执行setTimeout(() => {let result = onResolve(this.value);transferPromiseResult(promise, result, resolve, reject);});}// 当前 promise 实例调用了 rejectif (this.status === REJECTED) {// setTimeout 为了保证异步顺序执行setTimeout(() => {let result = onReject(this.value);transferPromiseResult(promise, result, resolve, reject);});}// 当前 promise 状态为 pending,把当前的 onResolve & onReject 缓存起来if (this.status === PENDING) {this.onResolveCallBack.push(() => {let result = onResolve(this.value);transferPromiseResult(promise, result, resolve, reject);});this.onRejectCallBack.push(() => {let result = onReject(this.value);transferPromiseResult(promise, result, resolve, reject);});}});return promise;
}

// 将上一次的 promise 的值,传递给下一个 then
function transferPromiseResult(promise, result, resolve, reject) {// 为了处理当前的 promise 实例,在当前的 then 被返回if (promise === result) {throw new TypeError('Chaining cycle detected for promise #<MyPromise>');}try {// 如果上一个 then 返回的是 MyPromise 的实例 && 不是同一个 promise 实例// 那只需要把 MyPromise 中的处理好的返回值传递给一下 then 即可if (result instanceof MyPromise) {result.then(resolve, reject);} else {// 正常结果,传给下一个 thenresolve(result);}} catch (error) {reject(error);}
} 

6. 实现 MyPromise.all() & MyPromise.race()

  • 上面我们实现了大部分的功能,这里的 all 和 race 都只需要基于已有功能实现即可.
  • all --> 接收多个 promise 的数组,当全部 promise 执行完,且状态都为 fulfilled,则返回结果集,否则返回失败的结果.
  • race --> 接收多个 promise 的数组,结果取最先完成的 promise 的结果,无论状态是 fulfilled 或者是 rejected.
// 三种状态
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// promise 接收⼀个函数参数,该函数会⽴即执⾏
class MyPromise {constructor(fn) {this.value;this.status = PENDING;this.onResolveCallBack = [];this.onRejectCallBack = [];// 执行传入fntry {fn(this.resolve.bind(this), this.reject.bind(this));} catch (error) {// 发生错误时,统一用 reject 方法处理this.reject(error);}}// 实例方法 resolveresolve(value) {if (this.status === PENDING) {this.value = value;this.status = RESOLVED;// setTimeout 保证异步执行setTimeout(() => {// 执行在 then 中存储的回调try {this.onResolveCallBack.forEach((fn) => fn && fn(this.value));} catch (error) {this.onRejectCallBack.forEach((fn) => fn && fn(error));}});}}// 实例方法 rejectreject(reason) {if (this.status === PENDING) {this.value = reason;this.status = REJECTED;// setTimeout 保证异步执行setTimeout(() => {// 执行在 then 中存储的回调this.onRejectCallBack.forEach((fn) => fn && fn(this.value));});}}// 静态方法 resolvestatic resolve(value) {return new MyPromise((resolve, reject) => {if (value instanceof MyPromise) {value.then(resolve, reject);} else {resolve(value);}});}// 静态方法 rejectstatic reject(value) {console.log(value);return new MyPromise((resolve, reject) => {if (value instanceof MyPromise) {value.then(resolve, reject);} else {reject(value);}});}// 静态方法 allstatic all(promiseArr) {// 记录每个成功的 promise 结果const resolveResultArr = [];return new MyPromise((resolve, reject) => {promiseArr.forEach(promise => {promise.then(value => {resolveResultArr.push(value);// 当两者的长度一致,表明所有 promise 执行完成,并结果都是成功的if (promiseArr.length === resolveResultArr.length) {resolve(resolveResultArr);}}, reason => {// 只要一个 promise 失败,就是失败reject(reason);});});});}// 静态方法 racestatic race(promiseArr) {return new MyPromise((resolve, reject) => {promiseArr.forEach(promise => {promise.then(value => {resolve(value);}, reason => {reject(reason);});});});}

}

MyPromise.prototype.then = function (onResolve, onReject) {// 保证 onResolve & onReject 为函数// 主要是为了 .then().then((v)=>v) 的情况,称之为 then 穿透onResolve = typeof onResolve === 'function' ? onResolve : (v) => v;onReject = typeof onReject === 'function' ? onReject : (v) => v;let promise = new MyPromise((resolve, reject) => {if (this.status === RESOLVED) {setTimeout(() => {// 把上一个 then 中返回的值保存 resultlet result = onResolve(this.value);// 传递值下一个 thentransferPromiseResult(promise, result, resolve, reject);});}if (this.status === REJECTED) {setTimeout(() => {// 相当于把上一个 then 中返回的值保存 resultlet result = onReject(this.value);// 传递值下一个 thentransferPromiseResult(promise, result, resolve, reject);});}// 在这里通过数组存储对应的回调,方便在状态改变之后进行调用if (this.status === PENDING) {this.onResolveCallBack.push(() => {// 把上一个 then 中返回的值保存 resultlet result = onResolve(this.value);// 传递值下一个 thentransferPromiseResult(promise, result, resolve, reject);});this.onRejectCallBack.push(() => {// 相当于把上一个 then 中返回的值保存 resultlet result = onReject(this.value);// 传递值下一个 thentransferPromiseResult(promise, result, resolve, reject);});}});return promise;
}

// 将上一次的 promise 的值,传递给下一个 then
function transferPromiseResult(promise, result, resolve, reject) {// 为了处理当前的 promise 实例,在当前的 then 被返回if (promise === result) {throw new TypeError('Chaining cycle detected for promise #<MyPromise>');}try {// 如果上一个 then 返回的是 MyPromise 的实例 && 不是同一个 promise 实例// 那只需要把 MyPromise 中的处理好的返回值传递给一下 then 即可if (result instanceof MyPromise) {result.then(resolve, reject);} else {// 正常结果,传给下一个 thenresolve(result);}} catch (error) {reject(error);}
} 

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值