对于promise的理解,我之前仅仅知道如何使用,最近我看了对于promise的详细解读,发现其实我对promise的理解太肤浅了。故写下这篇blog来梳理一下对promise的理解。同时也会结合网上的blog去实现一个稍微复杂点的promise。
promise实现 step by step
框架
- Promise构造函数接受一个函数参数exector,exector接受resolve和reject两个函数并立即执行,通过resolve/reject改变状态
状态改变后,触发原型链上的then、catch方法 ```javascript function Promise(exector){ const resolve = value => {
}
const reject = reason => {
} }
Promise.prototype.then = function(){} Promise.prototype.catch = function(){}
<a name="mPGTd"></a>## 一个能work的雏形- 定义三种状态、初始状态为`PENDING`,resolve和reject分别改变其状态。并将value值改变。- 将resolve和reject作为exector参数,调用exector并执行。并对其进行try..catch- then方法接受两个回调函数,作为状态改变后执行的回调。并用setTimeout模拟微任务。```javascript// 定义三种状态const PENDING = 'PENDING'; // 进行中const FULFILLED = 'FULFILLED'; // 已成功const REJECTED = 'REJECTED'; // 已失败function Promise(exector) {// 初始化状态this.status = PENDING;// 将成功、失败结果放在this上,便于then、catch访问this.value = undefined;this.reason = undefined;const resolve = value => {// 只有进行中状态才能更改状态if (this.status === PENDING) {this.status = FULFILLED;this.value = value;}}const reject = reason => {// 只有进行中状态才能更改状态if (this.status === PENDING) {this.status = REJECTED;this.reason = reason;}}try {// 立即执行executor// 把内部的resolve和reject传入executor,用户可调用resolve和rejectexector(resolve, reject);} catch(e) {// executor执行出错,将错误内容reject抛出去reject(e);}}Promise.prototype.then = function(onFulfilled, onRejected) {// then是微任务,这里用setTimeout模拟setTimeout(() => {if (this.status === FULFILLED) {// FULFILLED状态下才执行onFulfilled(this.value);} else if (this.status === REJECTED) {// REJECTED状态下才执行onRejected(this.reason);}})}
处理异步
想象一个这样的场景:
let promise = new Promise((resolve, reject) => {setTimeout(() => {resolve('success');}, 1000)})promise.then(value => {console.log(value)})
resolve方法是通过异步来调用。按照上面写的代码,调用then方法的时候,状态还没有改变。等到resolve方法被调用时,没有回调来响应。
如何处理异步呢?我们应该想到既然是resolve或者reject方法来改变状态,而状态改变就应该立即执行then方法,那么then方法的调用权是不是应该交给resolve方法才是?但是api暴露出去的是给promise实例对象调用。
这里,我们借助观察者模式的思想,在执行then时候,如果当前状态是PENNDING把回调函数保存起来, 在resolve方法里面调用。
// 定义三种状态const PENDING = 'PENDING'; // 进行中const FULFILLED = 'FULFILLED'; // 已成功const REJECTED = 'REJECTED'; // 已失败function Promise(exector) {// 初始化状态this.status = PENDING;// 将成功、失败结果放在this上,便于then、catch访问this.value = undefined;this.reason = undefined;this.onFulfilledCallbacks = undefined;this.onRejectedCallbacks = undefined;const resolve = value => {// 只有进行中状态才能更改状态if (this.status === PENDING) {this.status = FULFILLED;this.value = value;this.onFulfilledCallbacks(this.value);}}const reject = reason => {// 只有进行中状态才能更改状态if (this.status === PENDING) {this.status = REJECTED;this.reason = reason;this.onRejectedCallbacks(this.reason);}}try {// 立即执行executor// 把内部的resolve和reject传入executor,用户可调用resolve和rejectexector(resolve, reject);} catch(e) {// executor执行出错,将错误内容reject抛出去reject(e);}}Promise.prototype.then = function(onFulfilled, onRejected) {// then是微任务,这里用setTimeout模拟setTimeout(() => {if (this.status === FULFILLED) {onFulfilled(this.value);} else if (this.status === REJECTED) {onRejected(this.reason);} else {// 存储成功/失败回调this.onFulfilledCallbacks = onFulfilled;this.onRejectedCallbacks = onRejected;}})}
多次调用
所谓多次调用,就是如下场景:
let promise = new Promise((resolve, reject) => {setTimeout(() => {resolve('success');}, 1000)})promise.then(value => {console.log(value)})promise.then(value2 => {console.log(value2)})promise.then(value3 => {console.log(value3)})/** output should be like:successsuccesssuccess*/
但按照上面的代码,只会输出一个success,那是因为只保存了一个then的回调函数,并且靠后的then回调覆盖了前面的then回调。
把接受回调的变量改为数组解决问题:
// 定义三种状态const PENDING = 'PENDING'; // 进行中const FULFILLED = 'FULFILLED'; // 已成功const REJECTED = 'REJECTED'; // 已失败function Promise(exector) {// 初始化状态this.status = PENDING;// 将成功、失败结果放在this上,便于then、catch访问this.value = undefined;this.reason = undefined;// 成功态回调函数队列this.onFulfilledCallbacks = [];// 失败态回调函数队列this.onRejectedCallbacks = [];const resolve = value => {// 只有进行中状态才能更改状态if (this.status === PENDING) {this.status = FULFILLED;this.value = value;// 成功态函数依次执行this.onFulfilledCallbacks.forEach(fn => fn(this.value));}}const reject = reason => {// 只有进行中状态才能更改状态if (this.status === PENDING) {this.status = REJECTED;this.reason = reason;// 失败态函数依次执行this.onRejectedCallbacks.forEach(fn => fn(this.reason))}}try {// 立即执行executor// 把内部的resolve和reject传入executor,用户可调用resolve和rejectexector(resolve, reject);} catch(e) {// executor执行出错,将错误内容reject抛出去reject(e);}}Promise.prototype.then = function(onFulfilled, onRejected) {// then是微任务,这里用setTimeout模拟setTimeout(() => {if (this.status === FULFILLED) {onFulfilled(this.value);} else if (this.status === REJECTED) {onRejected(this.reason);} else {// 存储成功/失败回调this.onFulfilledCallbacks.push(onFulfilled);this.onRejectedCallbacks.push(onRejected);}})}
链式调用
下面这种形式就是链式调用,注意区别多次调用哦。
let promise = new Promise((resolve, reject) => {setTimeout(() => {resolve('success');}, 1000)})promise.then(value => {console.log(value)}).then(value2 => {console.log(value2)}).then(value3 => {console.log(value3)})
链式调用可以在then后继续接上then,从而无限套娃。这就要求then方法必须要返回一个promise实例对象。因此,我们在then方法中,整体框架如下所示:
Promise.prototype.then = function(onFulfilled, onRejected) {const self = this;let p2 = new Promise((resolve, reject) => {if (self.status === PENDING) {...} else if (self.status === FULFILLED) {...} else {...}})return p2;}
既然是一个promise实例了,肯定就要考虑什么时候调用resolve方法,和reject方法。
如果此时,self.status的状态为PENNDING,我们需要将onFulfilled``onRejected这两个回调push进对应的队列。直接就这样push进去吗?
HELL NO!! 我连这两个回调返回什么都不知道,万一返回的是一个异步操作呢?例如promise实例对象,那么是不是我这个p2的resolve方法调用时机应该在返回的promise实例对象完成后才能resolve?
因此,我们需要对push进队列的方法进行如下改造:
// 模拟微任务setTimeout(() => {const result = onFulfilled(self.value);result instanceof Promise ? result.then(resolve, reject) : resolve(result);})
首先我们应该先执行onFuilfilled方法拿到返回值检查下是不是promise,不是promise就直接调用resolve方法(这里resolve方法是p2的)
如果是promise,就应该等待他执行完,根据result这个promise的结果来判断调用resolve还是reject,所以这里把p2的resolve和reject作为then方法的回调函数。
完整then方法如下:
Promise.prototype.then = function (onFulfilled, onRejected) {// 保存this,为了获得外层promise对象的成员变量const self = this;const promise2 = new Promise((resolve, reject) => {// 针对promise内异步执行resolveif (self.status === PENDING) {self.onFulfilledCallbacks.push(() => {// try捕获错误try {// 模拟微任务setTimeout(() => {const result = onFulfilled(self.value);// 分两种情况:// 1. 回调函数返回值是Promise,执行then操作// 2. 如果不是Promise,调用新Promise的resolve函数// result如果是promise,则需要等待result结果再确定是resolve还是reject,所以把resolve reject作为// result.then的回调。注意这里的resolve和reject不是result的resolve和reject。是promise2的result instanceof Promise ? result.then(resolve, reject) : resolve(result);})} catch(e) {reject(e);}});self.onRejectedCallbacks.push(() => {// 以下同理try {setTimeout(() => {const result = onRejected(self.reason);// 不同点:此时是rejectresult instanceof Promise ? result.then(resolve, reject) : resolve(result);})} catch(e) {reject(e);}})} else if (self.status === FULFILLED) { // 针对promise内立即执行resolvesetTimeout(() => {try {const result = onFulfilled(self.value);result instanceof Promise ? result.then(resolve, reject) : resolve(result);} catch(e) {reject(e);}});} else if (self.status === REJECT){ // 针对promise内立即执行resolvesetTimeout(() => {try {const result = onRejected(self.error);result instanceof Promise ? result.then(resolve, reject) : resolve(result);} catch(e) {reject(e);}})}})return promise2;}
值穿透
then参数期望是函数,传入非函数则会发生值穿透。值传透可以理解为,当传入then的不是函数的时候,这个then是无效的。具体表现如下:
new Promise((resolve, reject) => {setTimeout(() => {resolve('success');}, 1000)}).then(2).then(3).then(value3 => {console.log(value3)})// output: success
原理上是当then中传入的不算函数,则这个promise返回上一个promise的值,这就是发生值穿透的原因,所以只需要对then的两个参数进行设置就行了:
Promise.prototype.then = function (onFulfilled, onRejected) {// 值穿透,then()参数期待为函数,如果不为函数,那将onFulfilled重置为返回resolve(value)中的valueonFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;onRejected = typeof onRejected === 'function'? onRejected:reason => { throw new Error(reason instanceof Error ? reason.message:reason) }...}
promise完整代码
// 定义三种状态const PENDING = 'PENDING'; // 进行中const FULFILLED = 'FULFILLED'; // 已成功const REJECTED = 'REJECTED'; // 已失败function Promise(exector) {// 初始化状态this.status = PENDING;// 将成功、失败结果放在this上,便于then、catch访问this.value = undefined;this.reason = undefined;// 成功态回调函数队列this.onFulfilledCallbacks = [];// 失败态回调函数队列this.onRejectedCallbacks = [];const resolve = value => {// 只有进行中状态才能更改状态if (this.status === PENDING) {this.status = FULFILLED;this.value = value;// 成功态函数依次执行this.onFulfilledCallbacks.forEach(fn => fn(this.value));}}const reject = reason => {// 只有进行中状态才能更改状态if (this.status === PENDING) {this.status = REJECTED;this.reason = reason;// 失败态函数依次执行this.onRejectedCallbacks.forEach(fn => fn(this.reason))}}try {// 立即执行executor// 把内部的resolve和reject传入executor,用户可调用resolve和rejectexector(resolve, reject);} catch(e) {// executor执行出错,将错误内容reject抛出去reject(e);}}Promise.prototype.then = function (onFulfilled, onRejected) {// 值穿透,then()参数期待为函数,如果不为函数,那将onFulfilled重置为返回resolve(value)中的valueonFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;onRejected = typeof onRejected === 'function'? onRejected:reason => { throw new Error(reason instanceof Error ? reason.message:reason) }// 保存this,为了获得外层promise对象的成员变量const self = this;const promise2 = new Promise((resolve, reject) => {// 针对promise内异步执行resolveif (self.status === PENDING) {self.onFulfilledCallbacks.push(() => {// try捕获错误try {// 模拟微任务setTimeout(() => {const result = onFulfilled(self.value);// 分两种情况:// 1. 回调函数返回值是Promise,执行then操作// 2. 如果不是Promise,调用新Promise的resolve函数// result如果是promise,则需要等待result结果再确定是resolve还是reject,所以把resolve reject作为// result.then的回调。注意这里的resolve和reject不是result的resolve和reject。是promise2的result instanceof Promise ? result.then(resolve, reject) : resolve(result);})} catch(e) {reject(e);}});self.onRejectedCallbacks.push(() => {// 以下同理try {setTimeout(() => {const result = onRejected(self.reason);// 不同点:此时是rejectresult instanceof Promise ? result.then(resolve, reject) : resolve(result);})} catch(e) {reject(e);}})} else if (self.status === FULFILLED) { // 针对promise内立即执行resolvesetTimeout(() => {try {const result = onFulfilled(self.value);result instanceof Promise ? result.then(resolve, reject) : resolve(result);} catch(e) {reject(e);}});} else if (self.status === REJECT){ // 针对promise内立即执行resolvesetTimeout(() => {try {const result = onRejected(self.error);result instanceof Promise ? result.then(resolve, reject) : resolve(result);} catch(e) {reject(e);}})}})return promise2;}Promise.prototype.catch= function (onRejected) {return this.then(null, onRejected);}
promise用法
case1 then(callback), callback中return作用
new Promise((resolve, reject) => {setTimeout(() => {resolve('success');}, 1000)}).then(value => {console.log(value)return 'Yes'}).then(value => {console.log(value)})/**output:successYes*/
很显然,根据之前链式调用的代码可知,then(onFulfilled, onRejected)方法中回调函数是这样执行的:
const result = onFulfilled(self.value);result instanceof Promise ? result.then(resolve, reject) : resolve(result);
如果有返回值(非Promise),那么调用resolve方法,传递给下一个then方法,如果没有return的话,那么这里的result就会得到一个undefined,同样的这个值也会传递到下一个then方法中。
case2 catch方法
catch方法在链式调用中写到最末尾,用于兜底错误处理。其原理就是调用then方法。
new Promise((resolve, reject) => {setTimeout(() => {resolve('success');}, 1000)}).then(value => {console.log(value)return new Promise((resolve, reject) => {reject('fuck');})}).then(value => {console.log(value)return 'sss'}).then(value => {console.log(value)}).catch(res => {console.log(res)})/** output:successfuck*/
case3 then(表达式)
new Promise((resolve, reject) => {setTimeout(() => {resolve('success');}, 1000)}).then(console.log('value'))
会立即输出value,因为then里面是表达式,不是函数,会当作函数的嵌套调用执行。
参考
大怪腿长一米八的promise系列文章
详细的Promise源码实现,再被面试问到轻松解答
Promise的源码实现(完美符合Promise/A+规范)
