手写一个Promise
什么是promise
1. Promise代码基本结构
实例化Promise对象时传入一个函数作为执行器,有两个参数(resolve和reject)分别将结果变为成功态和失败态。当实例化Promise时,构造函数中就要马上调用传入的executor函数执行。我们可以写出基本结构:
function Promise(executor) {
this.state = 'pending'; //状态
this.value = undefined; //成功结果
this.reason = undefined; //失败原因
function resolve(value) {};
function reject(reason) {};
executor(resolve, reject); // 立即执行
}
其中state属性保存了Promise对象的状态,规范中指明,一个Promise对象只有三种状态:等待态(pending)成功态(resolved)和失败态(rejected)。
当一个Promise对象执行成功了要有一个结果,它使用value属性保存;也有可能由于某种原因失败了,这个失败原因放在reason属性中保存。
2. 已经是成功态或是失败态不可再更新状态
规范中规定,当Promise对象已经由pending状态改变为了成功态(resolved)或是失败态(rejected)就不能再次更改状态了。因此我们在更新状态时要判断,如果当前状态是pending(等待态)才可更新:
function Promise(executor) {
let _this = this;
this.state = 'pending'; //状态
this.value = undefined; //成功结果
this.reason = undefined; //失败原因
function resolve(value) {
// 只有在pending状态下才可以改变状态
if (_this.state == 'pending') {
_this.value = value; // 保存成功结果
_this.state = 'resolved';
}
}
function reject(reason) {
// 只有在pending状态下才可以改变状态
if (_this.state == 'pending') {
_this.reason = reason; // 保存失败原因
_this.state = 'rejected';
}
}
// 如果executor执行报错,直接执行reject
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
以上可以看到,在resolve和reject函数中分别加入了判断,只有当前状态是pending才可进行操作,同时将成功的结果和失败的原因都保存到对应的属性上。之后将state属性置为更新后的状态。
3. then方法的基本实现
每一个Promise实例都有一个then方法,它用来处理异步返回的结果,它是定义在原型上的方法。当Promise的状态发生了改变,不论是成功或是失败都会调用then方法,所以,then方法的实现也很简单,根据state状态来调用不同的回调函数即可:
Promise.prototype.then = function (onFulfilled, onRejected) {
// 状态为resolved,执行onFulfilled,传入成功的值
if (this.state === 'resolved') {
//判断参数类型,是函数执行之
if (typeof onFulfilled === 'function') {
onFulfilled(this.value);
}
}
// 状态为rejected,执行onRejected,传入失败的原因
if (this.state === 'rejected') {
if (typeof onRejected === 'function') {
onRejected(this.reason);
}
}
};
4. 让Promise支持异步
代码写到这里似乎基本功能都实现了,可是还有一个很大的问题,目前此Promise还不支持异步代码,如果Promise中封装的是异步操作,then方法无能为力:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
},500);
});
p.then(data => console.log(data)); // 没有任何结果
运行以上代码发现没有任何结果,本意是等500毫秒后执行then方法,哪里有问题呢?原因是setTimeout函数使得resolve是异步执行的,有延迟,当调用then方法的时候,此时此刻的状态还是等待态(pending),因此then方法即没有调用onFulfilled也没有调用onRejected。
这个问题如何解决?我们可以参照发布订阅模式,在执行then方法时如果还在等待态(pending),就把回调函数临时寄存到一个数组里,当状态发生改变时依次从数组中取出执行就好了,清楚这个思路我们实现它,首先在类上新增两个Array类型的数组,用于存放回调函数:
function Promise(executor) {
let _this = this;
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledFunc = [];//保存成功回调
this.onRejectedFunc = [];//保存失败回调
//其它代码略...
}
这样当then方法执行时,若状态还在等待态(pending),将回调函数依次放入数组中:
Promise.prototype.then = function (onFulfilled, onRejected) {
//异步:还处于等待态,此时异步代码还没有走完
if (this.state == 'pending') {
if (typeof onFulfilled == 'function') {
this.onFulfilledFunc.push(onFulfilled); // 保存异步的成功回调
}
if (typeof onRejected == 'function') {
this.onRejectedFunc.push(onRejected); // 保存异步的失败回调
}
}
// 同步
if (this.state == 'resolved') {
if (typeof onFulfilled == 'function') {
onFulfilled(this.value);
}
}
if (this.state == 'rejected') {
if (typeof onRejected == 'function') {
onRejected(this.reason);
}
}
}
寄存好了回调,接下来就是当状态改变时执行就好了:
function Promise(executor) {
let _this = this;
this.state = 'pending' // 状态
this.value = undefined; // 成功结果
this.reason = undefined; // 失败原因
this.onFulfilledFunc = []; // 保存成功回调
this.onRejectedFunc = []; // 保存失败回调
function resolve(value) {
// 只有在pending状态下才可以改变状态
if (_this.state == 'pending') {
_this.value = value; // 保存成功结果
_this.onFulfilledFunc.forEach(fn => { fn(value) });
_this.state = 'resolved';
}
}
function reject(reason) {
// 只有在pending状态下才可以改变状态
if (_this.state == 'pending') {
_this.reason = reason; // 保存失败原因
_this.onRejectedFunc.forEach(fn => { fn(reason) });
_this.state = 'rejected';
}
}
// 如果executor执行报错,直接执行reject
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
至此,Promise已经支持了异步操作,setTimeout延迟后也可正确执行then方法返回结果。
检验下:
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('成功');
} else {
reject('失败');
}
})
})
promise.then((data) => {
console.log('success' + data);
}, (err) => {
console.log('err' + err);
})
通过以上代码,我们实现了一个简易版的promise,说简易版是因为我们的then方法只能调用一次,并没有实现原生promise中的链式调用。
后续更新中…