详细解答Promise源码
写这篇文章的目的是解剖Promise源码,起因也是最近秋招被问到了让手写Promise,另外在网上看到的Promise源码或多或少有些小问题,也就是没有完全遵循Promise/A+规范。
代码会完全使用ES6
语法,主要分以下几个模块:
- 整体分析(为代码书写铺路)
- 实现初版(构造函数大致功能亿完善)
- 支持异步和链式调用(完善then方法)
- 实现catch方法
- 实现Promise.resolve()
- 实现Promise.reject()
- 实现Promise.all()
- 实现Promise.race()
一、整体分析
所谓Promise就是一个容器,有三个状态:PENDING(进行中)、FULFILLED(成功)、REJECTED(失败),里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,有两大特点:
- 容器状态不受外界影响
- 一旦状态改变就不会再变,任何时候都可以得到这个结果
来看下Promise的用法:
new Promise((resolve, reject) => {
// ...
// 成功则执行resolve,否则指定reject
}).then(
res => {
// resolve对应触发函数的执行
},
err => {
// reject对应触发函数的执行
}
).then(
// 支持链式调用
res => {
}
).catch(
err => console.log(err)
)
Promise.resolve();
Promise.reject();
Promise.all([promise1, promise2, ...]).then();
Promise.race([promise1, promise2, ...]).then();
<span class="copy-code-btn">复制代码</span>
通过用法不难分析出:
- Promise构造函数接受一个函数参数exector,exector接受resolve和reject两个函数并立即执行,通过resolve/reject改变状态
- 状态改变后,触发原型链上的
then、catch
方法 - Promise类拥有静态方法
resolve、reject、all、race
那么可以写出大致结构代码:
class Promise {
constructor(exector) {
const resolve = () => {
}
const reject = () => {
}
exector(resolve, reject);
}
then() {
}
catch() {
}
static resolve() {
}
static reject() {
}
static all() {
}
static race() {
}
}
复制代码
之后在此基础上补充代码。
二、实现初版
首先引入三种状态,完善resolve
、reject
函数,最后在构造函数内执行exector(resolve, reject)
:
// 定义三种状态
const PENDING = 'PENDING'; // 进行中
const FULFILLED = 'FULFILLED'; // 已成功
const REJECTED = 'REJECTED'; // 已失败
class Promise {
constructor(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;
}
}
// 立即执行exector
// 把内部的resolve和reject传入executor,用户可调用resolve和reject
exector(resolve, reject);
}
}
复制代码
注意:exector(resolve, reject);
执行可能会报错,所以需要使用try
包括一下,有报错reject
抛出去。
constructor(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和reject
exector(resolve, reject);
} catch(e) {
// executor执行出错,将错误内容reject抛出去
reject(e);
}
}
复制代码
此时可以使用then
进行捕获了,then
接收两个函数,分别对应FULFILLED
和REJECTED
状态:
new Promise().then(
res => {},
err => {},
)
<span class="copy-code-btn">复制代码</span>
注意:then
、catch
是微任务,这里使用setTimeout模拟:
then(onFulfilled, onRejected) {
// then是微任务,这里用setTimeout模拟
setTimeout(() => {
if (this.status === FULFILLED) {
// FULFILLED状态下才执行
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// REJECTED状态下才执行
onRejected(this.reason);
}
})
}
复制代码
OK,初版已经完成:
// 定义三种状态
const PENDING = 'PENDING'; // 进行中
const FULFILLED = 'FULFILLED'; // 已成功
const REJECTED = 'REJECTED'; // 已失败
class Promise {
constructor(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和reject
exector(resolve, reject);
} catch(e) {
// executor执行出错,将错误内容reject抛出去
reject(e);
}
}
then(onFulfilled, onRejected) {
// then是微任务,这里用setTimeout模拟
setTimeout(() => {
if (this.status === FULFILLED) {
// FULFILLED状态下才执行
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// REJECTED状态下才执行
onRejected(this.reason);
}
})
}
}
复制代码
可以拿数据测试一下:
const promise = new Promise((resolve, reject) => {
Math.random() < 0.5 ? resolve(1) : reject(-1);
}).then(
res => console.log(res),
err => console.log(err),
)
复制代码
三、支持异步和链式调用
此时初版还有三个方向需要完善:
- Promise内部异步代码执行的问题。
- Promise的链式调用
- 值传透
支持异步代码
开发中经常会将接口放于promise
内部,等接口请求响应成功把数据resolve
出去,或失败时把数据reject
出去,此时then
、catch
才会进行捕获。
而现在的代码,promise
内部如果有异步代码执行后才resolve
,then
不会等待异步代码执行完毕会直接执行,所以此时状态是PENDING
,不会触发then
的回调函数。
新增onFulfilledCallbacks、onRejectedCallbacks维护成功态、失败态任务队列:
// 定义三种状态
const PENDING = 'PENDING'; // 进行中
const FULFILLED = 'FULFILLED'; // 已成功
const REJECTED = 'REJECTED'; // 已失败
class Promise {
constructor(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和reject
exector(resolve, reject);
} catch(e) {
// executor执行出错,将错误内容reject抛出去
reject(e);
}
}
then(onFulfilled, onRejected) {
// then是微任务,这里用setTimeout模拟
setTimeout(() => {
// 新增代码:
if (this.status === PENDING) {
// 状态是PENDING下执行
// 说明promise内部有异步代码执行,还未改变状态,添加到成功/失败回调任务队列即可
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}else if (this.status === FULFILLED) {
// FULFILLED状态下才执行
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// REJECTED状态下才执行
onRejected(this.reason);
}
})
}
}
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000);
}).then(
res => console.log(res)
)
// 1
复制代码
实现链式调用
Promise
的一大优势就是支持链式调用,具体来说就是then
方法的具体实现,实际上是返回了一个Promise
,需要注意的几个点:
- 保存之前promise实例的引用,即保存
this
- 根据
then
回调函数执行的返回值
- 如果是promise实例,那么返回的下一个promise实例会等待这个promise状态发生变化
- 如果不是promise实例,根据目前情况直接执行
resolve
或reject
完善then
函数:
then(onFulfilled, onRejected) {
// 保存this
const self = this;
return new Promise((resolve, reject) => {
if (self.status === PENDING) {
self.onFulfilledCallbacks.push(() => {
// try捕获错误
try {
// 模拟微任务
setTimeout(() => {
const result = onFulfilled(self.value);
// 分两种情况:
// 1. 回调函数返回值是Promise,执行then操作
// 2. 如果不是Promise,调用新Promise的resolve函数
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {
reject(e);
}
});
self.onRejectedCallbacks.push(() => {
// 以下同理
try {
setTimeout(() => {
const result = onRejected(self.reason);
// 不同点:此时是reject
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {
reject(e);
}
})
} else if (self.status === FULFILLED) {
setTimeout(() => {
try {
const result = onFulfilled(self.value);
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
} catch(e) {
reject(e);
}
});
} else if (self.status === REJECT){
setTimeout(() => {
try {
const result = onRejected(self.error);
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
} catch(e) {
reject(e);
}
})
}
})
}
复制代码
值传透
Promise
支持值穿透:
let promsie = new Promise((resolve,reject)=>{
resolve(1)
})
.then(2)
.then(3)
.then(value => {
console.log(value)
})
// 1
复制代码
then
参数期望是函数,传入非函数则会发生值穿透。值传透可以理解为,当传入then的不是函数的时候,这个then是无效的。
原理上是当then中传入的不算函数,则这个promise
返回上一个promise
的值,这就是发生值穿透的原因,所以只需要对then
的两个参数进行设置就行了:
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function'? onRejected:
reason => { throw new Error(reason instanceof Error ? reason.message:reason) }
复制代码
完整的then函数代码:
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function'? onRejected:
reason => { throw new Error(reason instanceof Error ? reason.message:reason) }
// 保存this
const self = this;
return new Promise((resolve, reject) => {
if (self.status === PENDING) {
self.onFulfilledCallbacks.push(() => {
// try捕获错误
try {
// 模拟微任务
setTimeout(() => {
const result = onFulfilled(self.value);
// 分两种情况:
// 1. 回调函数返回值是Promise,执行then操作
// 2. 如果不是Promise,调用新Promise的resolve函数
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {
reject(e);
}
});
self.onRejectedCallbacks.push(() => {
// 以下同理
try {
setTimeout(() => {
const result = onRejected(self.reason);
// 不同点:此时是reject
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {
reject(e);
}
})
} else if (self.status === FULFILLED) {
try {
setTimeout(() => {
const result = onFulfilled(self.value);
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
});
} catch(e) {
reject(e);
}
} else if (self.status === REJECTED){
try {
setTimeout(() => {
const result = onRejected(self.reason);
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {
reject(e);
}
}
});
}
复制代码
四、实现catch()方法
Promise.prototype.catch
就是Promise.prototype.then(null, onRejected)
的别名,所以实现就很简单了:
catch(onRejected) {
return this.then(null, onRejected);
}
复制代码
五、Promise.resolve()
这里就不考虑参数是thenable
对象了,那么参数有两种情况:
Promise
实例- 不是
Promise
实例
static resolve(value) {
if (value instanceof Promise) {
// 如果是Promise实例,直接返回
return value;
} else {
// 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED
return new Promise((resolve, reject) => resolve(value));
}
}
复制代码
六、Promise.reject()
Promise.reject
也会返回一个Promise实例,状态为REJECTED
。
与Promise.resolve
不同的是,Promise.reject
方法的参数会原封不动地作为reject
的参数
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason);
})
}
复制代码
七、Promise.all()
返回一个promise对象,只有当所有promise都成功时返回的promise状态才成功,需要注意的点是:
- 所有的promise状态变为
FULFILLED
,返回的promise状态才变为FULFILLED
。 - 一个promise状态变为
REJECTED
,返回的promise状态就变为REJECTED
。 - 数组成员不一定都是promise,需要使用
Promise.resolve()
处理。
static all(promiseArr) {
const len = promiseArr.length;
const values = new Array(len);
// 记录已经成功执行的promise个数
let count = 0;
return new Promise((resolve, reject) => {
for (let i = 0; i < len; i++) {
// Promise.resolve()处理,确保每一个都是promise实例
Promise.resolve(promiseArr[i]).then(
val => {
values[i] = val;
count++;
// 如果全部执行完,返回promise的状态就可以改变了
if (count === len) resolve(values);
},
err => reject(err),
);
}
})
}
复制代码
八、Promise.race()
Promise.race()
实现就比较简单了:
static race(promiseArr) {
return new Promise((resolve, reject) => {
promiseArr.forEach(p => {
Promise.resolve(p).then(
val => resolve(val),
err => reject(err),
)
})
})
}
复制代码
九、完整代码
// 模拟实现Promise
// Promise利用三大手段解决回调地狱:
// 1. 回调函数延迟绑定
// 2. 返回值穿透
// 3. 错误冒泡
// 定义三种状态
const PENDING = 'PENDING'; // 进行中
const FULFILLED = 'FULFILLED'; // 已成功
const REJECTED = 'REJECTED'; // 已失败
class Promise {
constructor(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和reject
exector(resolve, reject);
} catch(e) {
// executor执行出错,将错误内容reject抛出去
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function'? onRejected :
reason => { throw new Error(reason instanceof Error ? reason.message : reason) }
// 保存this
const self = this;
return new Promise((resolve, reject) => {
if (self.status === PENDING) {
self.onFulfilledCallbacks.push(() => {
// try捕获错误
try {
// 模拟微任务
setTimeout(() => {
const result = onFulfilled(self.value);
// 分两种情况:
// 1. 回调函数返回值是Promise,执行then操作
// 2. 如果不是Promise,调用新Promise的resolve函数
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {
reject(e);
}
});
self.onRejectedCallbacks.push(() => {
// 以下同理
try {
setTimeout(() => {
const result = onRejected(self.reason);
// 不同点:此时是reject
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {
reject(e);
}
})
} else if (self.status === FULFILLED) {
try {
setTimeout(() => {
const result = onFulfilled(self.value);
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
});
} catch(e) {
reject(e);
}
} else if (self.status === REJECTED) {
try {
setTimeout(() => {
const result = onRejected(self.reason);
result instanceof Promise ? result.then(resolve, reject) : resolve(result);
})
} catch(e) {
reject(e);
}
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
static resolve(value) {
if (value instanceof Promise) {
// 如果是Promise实例,直接返回
return value;
} else {
// 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED
return new Promise((resolve, reject) => resolve(value));
}
}
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason);
})
}
static all(promiseArr) {
const len = promiseArr.length;
const values = new Array(len);
// 记录已经成功执行的promise个数
let count = 0;
return new Promise((resolve, reject) => {
for (let i = 0; i < len; i++) {
// Promise.resolve()处理,确保每一个都是promise实例
Promise.resolve(promiseArr[i]).then(
val => {
values[i] = val;
count++;
// 如果全部执行完,返回promise的状态就可以改变了
if (count === len) resolve(values);
},
err => reject(err),
);
}
})
}
static race(promiseArr) {
return new Promise((resolve, reject) => {
promiseArr.forEach(p => {
Promise.resolve(p).then(
val => resolve(val),
err => reject(err),
)
})
})
}
}
复制代码