前言
Promise相信大家都很熟悉,而手写一个Promise也是大厂面试的热点,那么让我们一步步的手写一个Promise吧
搭建Promise的骨架
根据promise/A+规范,Promsie构造函数返回一个promise对象实例,这个对象具有一个then方法。在then方法中,有两个函数类型的参数,分别是onfulfilled和onrejected。
其中,onfulfilled通过参数可以获取Promise对象经过resolve处理后的值,onrejected可以获取Promise对象经过reject处理后的值。
我们先根据规范把构造函数和then方法写好
function Promise(executor){
}
Promise.prototype.then=function(onfulfilled,onrejected){
}
在使用new调用Promise构造函数时,往往是异步操作结束时调用executor的参数resolve,并将经过resolve处理后的值,可以在后续的then方法的第一个函数参数中第一个函数参数onfulfilled中拿到;当出现错误时,调用executor的参数reject,并将错误信息作为reject的函数参数执行,这个错误信息可以在后续then方法的第二个参数onrejected中得到,代码如下
function Promise(executor){
this.status = 'pending'
this.value = null
this.reason = null
//箭头函数保证resolve和reject方法能够拿到Promise实例的值
const resolve=value=>{
this.value =value
}
const reject=reason=>{
this.reason = reason
}
executor(resolve,reject)
}
Promise.prototype.then = function(onfulfilled ,onrejected){
onfulfilled(this.value)
onrejected(this.error)
}
完善Promise的状态
我们测试一下之前的代码
let promise = new Promise((resolve,reject) =>{
resolve('data')
reject('error')
})
promise.then(data=>{
console.log(data)
},error =>{
console.log(error)
})
执行上面代码将会输出data和error,而正常的promise会输出data
因为Promise的实例状态只能从pending变为fulfilled,或者从pending变为rejected。而且状态一旦变更完毕,就不可能再次变化。代码如下
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
//在resovle和reject方法中加入了判断,只允许Promsie实例状态从pending变为fulfilled,或者从pending变为rejected
const resolve = value => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
}
}
const reject = reason => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
}
}
executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled , onrejected ) {
if (this.status === 'fulfilled'){
onfulfilled(this.value)
}
if (this.status === 'rejected'){
onrejected(this.reason)
}
}
ok,我们的代码可以顺利执行了,但是Promise是用来解决异步问题的,而我们的代码是同步执行的,别着急,下面我们就完善最重要的异步逻辑
编写Promise的异步逻辑
异步初步实现
我们先测试一下如下代码
let promise = new Promise((resolve,reject) =>{
setTimeout(()=>{
resolve('data')
},2000)
})
promise.then(data =>{
console.log(data)
})
等待2s,发现并没有任何东西输出?^?而正常情况下会在两秒后输出data
原因是我们的then方法中的onfulfilled是同步执行的,它在执行时this.status依然为pending,并没有做到2s后在执行onfulfilled。
我们应该在合适的时间调用onfulfilled方法,这个时间就是开发者调用resolve时。所以我们先在状态为pending时把开发者传进来的onfulfilled方法存起来,再在resolve方法中执行即可。代码如下
function Promise(executor) {
this.status = "pending";
this.value = null;
this.reason = null;
//设置默认值为函数元
this.onfulfilledFunc = Function.prototype;
this.onrejectedFunc = Function.prototype;
const resolve = (value) => {
//判断value是否为Promise实例,如果是先对它执行then方法
if (value instanceof Promise) {
return value.then(resolve, reject);
}
if (this.status === "pending") {
this.value = value;
this.status = "fulfilled";
this.onfulfilledFunc(this.value);
}
};
const reject = (reason) => {
if (this.status === "pending") {
this.reason = reason;
this.status = "rejected";
this.onrejectedFunc(this.reason);
}
};
executor(resolve, reject);
}
Promise.prototype.then = function (onfulfilled, onrejected) {
if (this.status === "fulfilled") {
onfulfilled(this.value);
}
if (this.status === "rejected") {
onrejected(this.reason);
}
if (this.status === "pending") {
this.onfulfilledFunc = onfulfilled;
this.onrejectedFunc = onrejected;
}
};
放入任务队列
我们看下面这个测试
let promise = new Promise((resolve, reject) => {
resolve("data");
});
promise.then((data) => {
console.log(data);
});
console.log(1);
我们代码输出了data,再输出了1。
而正常的话,先输出1,再输出data。
原因是console.log(1)是同步任务,只有当同步任务的栈为空时,才会执行异步的任务队列。因此需要将resolve与reject放到任务队列中。
我们就简单的放到setTimeout中,保证异步执行(其实Promise属于microtasks,这样做并不严谨,可以用MutationObserver来模拟),代码如下
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onfulfilledFunc = Function.prototype
this.onrejectedFunc = Function.prototype
const resolve = value => {
if(value instanceof Promise){
return value.then(resolve,reject)
}
setTimeout(()=>{
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onfulfilledFunc(this.value)
}
})
}
const reject = reason => {
setTimeout(()=>{
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onrejectedFunc(this.reason)
}
})
}
executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled , onrejected ) {
if (this.status === 'fulfilled'){
onfulfilled(this.value)
}
if (this.status === 'rejected'){
onrejected(this.reason)
}
if (this.status === 'pending'){
this.onfulfilledFunc = onfulfilled
this.onrejectedFunc = onrejected
}
}
储存回调函数
看下面测试
// 测试
let promise = new Promise((resolve,reject) =>
{
setTimeout(()=>{
resolve('data')
},2000)
})
promise.then(data=>{
console.log(`1:${data}`)
})
promise.then(data=>{
console.log(`2:${data}`)
})
理论上应该输出
1:data
2:data
但只输出了2:data原因是第二个then方法中的onFulfilledFunc会覆盖第一个then方法中的onFulfilledFunc
那我们需要将then方法的onFulfilledFunc储存到一个数组中,在Promise被决议时依次执行onFulfilldedArray数组内的方法即可。而onRejected同理,代码如下
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onfulfilledArray = []
this.onrejectedArray = []
const resolve = value => {
if(value instanceof Promise){
return value.then(resolve,reject)
}
setTimeout(()=>{
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
//forEach方法依次执行方法
this.onfulfilledArray.forEach(func=>{
func(value)
})
}
})
}
const reject = reason => {
setTimeout(()=>{
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onrejectedArray.forEach(func=>{
func(reason)
})
}
})
}
//如果在构造函数中出错,将会自动触发Promsie实例状态变为rejected
try{
executor(resolve, reject)
}catch(e){
reject(e)
}
}
Promise.prototype.then = function (onfulfilled , onrejected ) {
if (this.status === 'fulfilled'){
onfulfilled(this.value)
}
if (this.status === 'rejected'){
onrejected(this.reason)
}
if (this.status === 'pending'){
//将onfulfilled储存到一个数组中
this.onfulfilledArray.push(onfulfilled)
this.onrejectedFunc.push(onrejected)
}
}
接下来继续实现then方法的链式调用效果
promsie then 的链式调用
链式调用的初步实现
首先看如果第一个then方法返回非promise实例的情况
let promise = new Promise((resolve,reject) =>
{
setTimeout(()=>{
resolve('data')
},3000)
})
promise.then(data=>{
console.log(`first ${data}`)
return `second ${data} `
})
.then(data=>{
console.log(data)
})
每个then方法都应该返回一个promise实例
Promise.prototype.then = function (onfulfilled , onrejected ) {
//Promise穿透实现,如果给then()函数传递非函数类型的值时,则给一个默认值,该默认值是返回其参数的函数
onfulfilled = typeof onfulfilled === 'function'?onfulfilled:data=>data
onrejected = typeof onrejected === 'function' ? onrejected:error=>{throw error};
// promise2将作为then方法的返回值
let promise2
if (this.status === 'fulfilled'){
return promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
try{
//这个新的promise2的经过resolve处理的值为 onfulfilled 的执行结果
let x = onfulfilled(this.value)
resolve(x)
}catch(e){
reject(e)
}
})
})
}
if (this.status === 'rejected'){
return promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
try {
//这个新的promise2的经过resolve处理的值为 onrejected的执行结果
let x = onrejected(this.value)
resolve(x)
} catch (e) {
reject(e)
}
})
})
}
//promise2 在异步处理结束后,依次执行onfulfilledArray或onRejectedArray数组的函数时,被决议
//而onfulfilledArray或onRejectedArray数组的函数切换promise2的状态,并进行决议
if (this.status === 'pending'){
return promise2 =new Promise((resolve,reject)=>{
this.onfulfilledArray.push(()=>{
try{
let x = onfulfilled(this.value)
resolve(x)
}catch(e){
reject(e)
}
})
this.onrejectedArray.push(()=>{
try{
let x = onrejected(this.reason)
resolve(x)
}
catch(e){
reject(e)
}
})
})
}
}
代码执行成功
完善链式调用
我们看这个例子
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('data')
}, 3000)
})
promise.then(data => {
console.log(data)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${data} next`)
}, 1000)
})
})
.then(data => {
console.log(data)
})
根据promise/A+规范,x既可以是一个普通值,也可以是一个promise实例,所以我们抽象出resolvePromise方法进行统一处理,代码如下
Promise.prototype.then = function (onfulfilled , onrejected ) {
onfulfilled = typeof onfulfilled === 'function'?onfulfilled:data=>data
onrejected = typeof onrejected === 'function' ? onrejected:error=>{throw error};
let promise2
if (this.status === 'fulfilled'){
return promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
try{
let x = onfulfilled(this.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
})
}
if (this.status === 'rejected'){
return promise2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
try {
let x = onrejected(this.value)
resolvePromise(promise2,x,resolve,reject)
} catch (e) {
reject(e)
}
})
})
}
if (this.status === 'pending'){
return promise2 =new Promise((resolve,reject)=>{
this.onfulfilledArray.push(()=>{
try{
let x = onfulfilled(this.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
this.onrejectedArray.push(()=>{
try{
let x = onrejected(this.reason)
resolvePromise(promise2,x,resolve,reject)
}
catch(e){
reject(e)
}
})
})
}
}
最后实现一下resolvePromise函数
const resolvePromise = (promise2, x, resolve, reject) => {
//当x和promise2相等时,也就是在onfulfilled返回promise2时,执行reject
if (x === promise2) {
reject(new TypeError('error due to circular reference'))
}
//是否已经执行过onfulfilled或onrejected
let consumed = false
let thenable
//如果返回的是Promsie类型
if (x instanceof Promise) {
if (x.status === 'pending') {
x.then(function (data) {
//递归调用
resolvePromise(promise2, data, resolve, reject)}, reject
)
} else {
x.then(resolve, reject)
}
return
}
let isComplexResult = targert => (typeof targert === 'function' || typeof targert === 'object') && (targert !== null)
//如果返回的是疑似Promsie类型
if (isComplexResult(x)) {
try {
thenable = x.then
//判断返回值是否为promise类型
if (typeof thenable === 'function') {
thenable.call(x, function (data) {
if (consumed) {
return
}
consumed = true
//递归调用
return resolvePromise(promise2, data, resolve, reject)
}, function (error) {
if (consumed) {
return
}
consumed = true
return reject(error)
})
}
else {
resolve(x)
}
} catch (e) {
if (consumed) {
return
}
consumed = true
return reject(e)
}
}
//如果是普通值的话
else {
resolve(x)
}
}
自此我们彻底搞定了then的链式调用,这也是手写promise最核心的部分,下一篇我们来一起实现catch方法以及其他的静态方法吧