promise,在项目开发应该大家都不陌生吧,今天我们来看下promise是怎么实现,自己手写一个promise。
首先我们来看封装一个promise需要什么条件
let promise = new Promise((resolve,reject) => {})
console.log(promise)
由上可知,我们看到promise身上有状态,返回值,__proto__上有一些方法,所以我们可以采用class的方式来创建。
// 手写promise
class XG {
// 属于promise类的静态属性
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(executor){
// 设置XG初始化身上的值
this.status = XG.PENDING
this.value = null
// 绑定promise,resolve,reject方法,注意这里this指向问题
executor(this.resolve.bind(this), this.reject.bind(this))
}
// promise 成功捕捉
resolve(value) {
console.log(this.status)
// 设置条件只有在pending状态下才可以改变状态
if (this.status == XG.PENDING) {
// 修改status状态
this.status = XG.FULFILLED
this.value = value
}
}
// promise 失败捕捉
reject(value) {
// 设置条件只有在pending状态下才可以改变状态
if (this.status == XG.PENDING) {
// 修改status状态
this.status = XG.REJECTED
this.value = value
}
}
}
这样就一个简易的promise出来了,接下来是封装then函数。
// 设置then函数中设置resolve,reject两个函数返回
then(onFulfilled = () => {}, onRejected = () => {}) {
// 设置条件只有在fulfilled状态下才可以改变状态
if (this.status == XG.FULFILLED) {
onFulfilled(this.value);
}
// 设置条件只有在rejected状态下才可以改变状态
if (this.status == XG.REJECTED) {
onRejected(this.value);
}
}
在这就完成我们的then函数基本构造, 引入我们封装的js,改变reject状态可见在浏览器中完美输出。
<script src="./promise.js"></script>
<script>
new XG((resolve,reject) => {
// resolve('成功状态')
reject('失败状态')
}).then(res => {
console.log(res)
},error => {
console.log(error)
})
// let promise = new Promise((resolve,reject) => {})
// console.log(promise)
</script>
现在有一个问题,因为js是单线程执行下来,是会先执行同步,在执行微任务,最后执行宏任务,在promise中,then函数是会被放入微任务队列当中。现在我们封装的then方法是会跟同步一起执行,所以我们需要在then函数中设置一下。
// 设置then函数中设置resolve,reject两个函数返回
then(onFulfilled = () => {}, onRejected = () => {}) {
// 设置条件只有在fulfilled状态下才可以改变状态
if (this.status == XG.FULFILLED) {
// 把任务加入宏任务队列中
setTimeout(() => {
// 对我们的then中的错误捕捉
try {
onFulfilled(this.value);
} catch (error) {
onRejected(error)
}
});
}
// 设置条件只有在rejected状态下才可以改变状态
if (this.status == XG.REJECTED) {
// 把任务加入宏任务队列中
setTimeout(() => {
// 对我们的then中的错误捕捉
try {
onRejected(this.value);
} catch (error) {
onRejected(error)
}
})
}
}
let promise = new XG((resolve,reject) => {
reject('失败状态')
}).then(res => {
console.log(res)
},error => {
console.log(error,'微任务')
})
console.log('同步代码')
现在有一个问题是当我们在一秒钟后改变状态的时候,并不会触发then函数中状态捕获,因为,在new 类的时候,then函数已经执行,所以我们需要在then函数中对pending状态捕获。
// 设置一个数组,将未来需要变化的状态函数存放进去
this.callbacks = [];
// 在then中对pending状态捕捉
if (this.status == XG.PENDING) {
// 当前状态为pending状态时,提前将onFulfilled,onRejected存放进去
this.callbacks.push({
// 延迟后对错误的处理
onFulfilled: value => {
try {
onFulfilled(value)
} catch (error) {
onRejected(error)
}
},
// 延迟后对错误的处理
onRejected: value => {
try {
onRejected(value)
} catch (error) {
onRejected(error)
}
}
})
}
// 当延迟执行resolve函数时,遍历数组,执行状态成功方法
this.callbacks.filter(item => {
item.onFulfilled(value)
})
// 当延迟执行reject函数时,遍历数组,执行状态成功方法
this.callbacks.filter(item => {
item.onRejected(value)
})
这样就解决延迟后对状态的捕获。这样还有一个问题,当延时后改变状态时,有同步代码,会先改变状态,所以我们需要把改变状态变为异步操作。
// 状态为padding状态时,加入宏任务队列
setTimeout(() => {
// 当延迟执行resolve函数时,遍历数组,执行状态成功方法
this.callbacks.filter(item => {
item.onRejected(value)
})
});
// 状态为padding状态时,加入宏任务队列
setTimeout(() => {
// 当延迟执行reject函数时,遍历数组,执行状态成功方法
this.callbacks.filter(item => {
item.onFulfilled(value)
})
});
promise中的then函数是可以连续点then, then是返回一个新的promise,和之前的promise没有关系, 所以在这,我们的then函数需要进一步封装。
// 设置then函数中设置resolve,reject两个函数返回
then(onFulfilled = () => {}, onRejected = () => {}) {
// 每次then函数都返回出一个新的pormise实例
return new XG((resolve,reject) => {
if (this.status == XG.PENDING) {
// 当前状态为pending状态时,提前将onFulfilled,onRejected存放进去
this.callbacks.push({
// 延迟后对错误的处理
onFulfilled: value => {
try {
// 获取成功状态的返回值,将返回值由下一次成功状态抛出
let result = onFulfilled(value)
resolve(result);
} catch (error) {
onRejected(error)
}
},
// 延迟后对错误的处理
onRejected: value => {
try {
// 获取成功状态的返回值,将返回值由下一次成功状态抛出
let result = onRejected(value)
resolve(result);
} catch (error) {
onRejected(error)
}
}
})
}
// 设置条件只有在fulfilled状态下才可以改变状态
if (this.status == XG.FULFILLED) {
// 把任务加入宏任务队列中
setTimeout(() => {
// 对我们的then中的错误捕捉
try {
// 获取成功状态的返回值,将返回值由下一次成功状态抛出
let result = onFulfilled(this.value);
resolve(result);
} catch (error) {
onRejected(error)
}
});
}
// 设置条件只有在rejected状态下才可以改变状态
if (this.status == XG.REJECTED) {
// 把任务加入宏任务队列中
setTimeout(() => {
// 对我们的then中的错误捕捉
try {
// 获取成功状态的返回值,将返回值由下一次成功状态抛出
let result = onRejected(this.value);
resolve(result);
} catch (error) {
onRejected(error)
}
})
}
})
}
让我们引入封装的promise,验证一下。
let promise = new XG((resolve,reject) => {
resolve('成功')
}).then(res => {
console.log(res)
return 'two2'
},error => {
console.log(error,'微任务')
return '成功'
}).then(res => {
console.log('成功:' + res)
},error => {
console.log('失败:' + error)
这里要注意一点的是,当我们使用原生promise,抛出状态时,then函数中没有写函数,状态值会被下一次then抛出来。
let promise = new Promise((resolve,reject) => {
resolve('成功')
})
.then()
.then(res => {
console.log(res)
},error => {
console.log(error);
}
)
所以我们需要给then函数中设置默认函数返回值。
then(onFulfilled = () => this.value, onRejected = () => this.value) {}
现在有一个问题是原生promise函数.then中可以new 出一个新的实例,下一次then函数中可以获取到这个值。
let promise = new Promise((resolve,reject) => {
resolve('成功')
})
.then(res => {
return new Promise((resolve, reject) => {
resolve('新的promise')
})
})
.then(res => {
console.log(res)
})
接下来引用我们的promise查看一下。
let promise = new XG((resolve,reject) => {
resolve('成功')
})
.then(res => {
return new XG((resolve, reject) => {
resolve('新的promise')
})
})
.then(res => {
console.log(res);
})
我们的promise中下一次then函数获取到得是整个promise,所以我们需要吧then函数改变一下。
// 在then函数中我们需要对 FULFILLED 和 REJECTED 和 FULFILLED 状态下进行判断
// 判断一下当前返回值是否由XG类实现,是的话他就是一个promise
let result = onFulfilled(this.value);
if (result instanceof XG) {
// 调用它的then函数,改变状态,把返回值抛出
result.then(res => {
resolve(res)
}, reason => {
reject(reason)
})
} else {
resolve(result);
}
以上代码有点些繁琐,我们可以简化下
let result = onFulfilled(this.value);
// 判断一下当前返回值是否由XG类实现,是的话他就是一个promise
if (result instanceof XG) {
// 下面resolve,reject是之前new XG身上的函数,返回新的promise的then会调用这个两个函数,同时也会把参数传入这两个函数。
result.then(resolve,reject)
} else {
resolve(result);
}
try {
// 判断一下当前返回值是否由XG类实现,是的话他就是一个promise
if (result instanceof XG) {
// 调用它的then函数,改变状态,把返回值抛出
result.then(resolve,reject)
} else {
resolve(result);
}
} catch (error) {
reject(error)
}
上边的三个状态的代码重复性比较多,我们可以把相同部分提出过,封装成一个函数。
// 函数复用
multiplex(result, resolve, reject) {
// 对我们的then中的错误捕捉
try {
// 判断一下当前返回值是否由XG类实现,是的话他就是一个promise
if (result instanceof XG) {
// 调用它的then函数,改变状态,把返回值抛出
result.then(resolve,reject)
} else {
resolve(result);
}
} catch (error) {
reject(error)
}
}
// PENDING 状态下
// 注意是PENDING 下的value是由上一次resolve传入过来
this.multiplex(onFulfilled(value), resolve, reject);
this.multiplex(onRejected(value), resolve, reject);
// FULFILLED 状态下
this.multiplex(onFulfilled(this.value), resolve, reject);
// REJECTED 状态下
this.multiplex(onRejected(this.value), resolve, reject);
在原生 promise 中,then函数中不允许返回自己本身一样的promise
let promise = new Promise((resolve,reject) => {
resolve('成功')
})
let p = promise.then(res => {
return p
})
我们封装的 then 方法没有对这一错误进行捕捉,我们需要重新封装一下
// 把返回新的promise 封装给一个对象
let promise = new XG((resolve,reject) => {}
// 由于是在宏任务当中,是异步执行,可以访问自己,把当前promise对象传入复用函数当中
this.multiplex(promise, onFulfilled(value), resolve, reject);
// 函数复用
multiplex(promise, result, resolve, reject) {
// 判断是否是自己本身
if (promise == result) {
// 主动抛出错误
throw new TypeError("Chaining cycle detected")
}
// 对我们的then中的错误捕捉
try {
// 判断一下当前返回值是否由XG类实现,是的话他就是一个promise
if (result instanceof XG) {
// 调用它的then函数,改变状态,把返回值抛出
result.then(resolve,reject)
} else {
resolve(result);
}
} catch (error) {
reject(error)
}
}
在原生promsie当中,是可以直接Promise.resolve(“成功”),进行改变状态,可以在then函数当时获取到resolve当中的值。
Promise.resolve("成功").then(res => {
console.log(res);
})
Promise.reject("失败").then(null,reason => {
console.log(reason);
})
所以我们需要在封装的promise定义两个静态属性方法
// 封装自定属性resolve方法。
static resolve(value) {
// 每次调用都是返回一个新的promise实例。
return new XG((resolve,reject) => {
// 改变当前的状态,并把传入进来的值抛出去
resolve(value)
})
}
// 引入我们的XG类
XG.resolve("成功").then(res => {
console.log(res);
})
上边是对成功状态的捕捉,现在还有一种情况是当我们在resolve(),传入一个promise,后面的then函数是对传入的promise的状态捕捉,所以我们需要在resolve中判断一下。
// 封装自定属性resolve方法。
static resolve(value) {
// 每次调用都是返回一个新的promise实例。
return new XG((resolve, reject) => {
// 判断传入进来的参数是不是一个prosmie,如果是调用它的then函数改变方法,否则直接成功状态返回。
if (value instanceof XG) {
value.then(resolve, reject)
} else {
// 改变当前的状态,并把传入进来的值抛出去
resolve(value)
}
})
}
// 引入我们的XG类
let p = new XG((resolve, reject) => {
reject("失败")
})
XG.resolve(p).then(null, reason => {
console.log(reason);
})
测试成功,接下来是是对reject()函数的封装。
// 封装自定属性reject方法。
static reject(value) {
// 每次调用都是返回一个新的promise实例。
return new XG((resolve, reject) => {
// 判断传入进来的参数是不是一个prosmie,如果是调用它的then函数改变方法,否则直接成功状态返回。
if (value instanceof XG) {
value.then(resolve, reject)
} else {
// 改变当前的状态,并把传入进来的值抛出去
reject(value)
}
})
}
接下来是封装all()方法,promise中的all中是返回一个新的promise,在then()方法中进行获取实例值,这里有一点要注意的是,then() 中捕捉的状态必须统一,否则会进入第二个参数reason当中。
let p1 = new Promise(resolve => {
resolve("p1-成功")
})
let p2 = new Promise((resolve, reject) => {
resolve("p2-成功")
})
Promise.all([p1, p2]).then(res => {
console.log(res)
})
接下来是我们的all方法封装。
// 封装自定属性all方法。
static all(proArr) {
// 创建resolve数组。
const values = []
// 返回一个新的promise实例
return new XG((resolve, reject) => {
proArr.forEach(item => {
item.then(res => {
// 把当前所有resolve状态下添加进去
values.push(res)
// 判断resolve数组添加进来的数量是不是等于传入进来的数量,等于的话,全部resolve状态抛出。
if (values.length == proArr.length) {
resolve(values)
}
// 如果传入近来有一个reject状态,就会reject状态抛出
}, resaon => {
reject(resaon)
})
})
})
}
let p1 = new XG(resolve => {
resolve("p1-成功")
})
let p2 = new XG((resolve, reject) => {
reject("p2-失败")
})
XG.all([p1, p2]).then(res => {
console.log(res)
}, reason => {
console.log(reason);
})
接下来是对race()函数封装,原生的race方法是对异步传入进来的promise状态,耗时最短的promise优先抛出,后续不在执行。
let p1 = new Promise(resolve => {
setTimeout(() => {
resolve("p1-成功")
}, 1000);
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("p2-失败")
}, 100);
})
Promise.race([p1, p2]).then(res => {
console.log(res)
}, reason => {
console.log("reject:" + reason);
})
搞清楚race()的特性,接下来使我们的race()函数的封装。
// 封装自定属性race方法。
static race(proArr) {
// 返回一个新的promise实例
return new XG((resolve, reject) => {
proArr.forEach(item => {
item.then(res => {
// 成功状态抛出
resolve(res)
}, resaon => {
// 失败状态抛出
reject(resaon)
})
})
})
}
let p1 = new XG(resolve => {
setTimeout(() => {
resolve("p1-成功")
}, 1000);
})
let p2 = new XG((resolve, reject) => {
setTimeout(() => {
reject("p2-失败")
}, 2000);
})
XG.race([p1, p2]).then(res => {
console.log(res)
}, reason => {
console.log("reject:" + reason);
})
有对promise底层感兴趣的朋友可以去 后盾人promise学习手册 进行学习。
项目地址 https://github.com/xiaoguo66/promise