带你一步一步手写Promise(保姆级讲解,你看你也会写)

前言

今天我们要来实现一个手写的Promise。如果你对JavaScript的Promise还不是很熟悉,或者想深入了解它的内部机制,那么这篇文章非常适合你,跟着我从零开始,一步步构建Promise吧!

正文

1.初始结构

创建类

let promise=new Promise((resolve,reject)=>{})

我们通常用Promise都是这样new一个实例对吧,那我们就用class创建一个Promise类,如下:

class MyPromise {
    constructor(executor) {
        const resolve = (value) => {}
        const reject = (reason) => {}
        executor(resolve, reject)
      }

我们可以看到上面的例子,我们在new一个promise实例的时候,肯定是需要传入参数的,这个参数是一个函数,而且当我们传入这个函数参数的时候,这个函数参数会被自动执行,所以我们在类的construct里面添加一个参数exector,并且在里面执行一下这个参数,因为原生Promsie里面可以传入传入resolvereject两个参数,所有我们创建两个函数resolvereject,并把它传入exector

创建所需属性和方法

let promise=new Promise((resolve,reject)=>{
    resolve('成功')
})

例子中我们知道resolve()可以改变promsie状态,promsie有三个状态,分别是pendingfulfilledrejected,并且呢只能是pending=>fulfilled,pending=>rejected,其它都不可以,所有我们提前把这些状态定义好,我们就用static来创建静态属性,并且在constructor里面添加一个state(类里面的this是指向new出来的实例对象的)状态为MyPromise.PENDING(静态属性可以通过类名.属性来访问到)也就是pending状态,这样每个实例创建后就会有自生的属性来判断及变动了,并且我们就可以在自己写的resolvereject函数里面来改变状态了

class MyPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(executor) {
        this.state = MyPromise.PENDING
        this.value = undefined
        this.reason = undefined
    
        const resolve = (value) => { 
          if (this.state === MyPromise.PENDING) { // fulfilled状态的上一种状态只能是 pending,状态一经变更就不在逆转
            this.state = MyPromise.FULFILLED
            this.value = value
          }
        }
    
        const reject = (reason) => {
          if (this.state === MyPromise.PENDING) {  // 同上
            this.state = MyPromise.REJECTED
            this.reason = reason
          }
        }
    
        executor(resolve, reject)
    }
    
}

看上面原生的promise例子可以知道,resolvereject是可以传入参数的,所有分别创建两个value和reason作为它俩的参数,并且我们把参数赋值给实例的valureason属性。

2.then的实现

我们接着来实现then方法

let promise=new Promise((resolve,reject)=>{
    resolve('成功')
    reject('失败')
})
promise.then(
    value=>console.log(value),   
    reason=>console.log(reason)
)

let promise1=new Promise((resolve,reject)=>{
    resolve('成功')
})
promise1.then(
    value=>{console.log(value)},   
    reason=>{console.log(reason)}
)

let promise2=new Promise((resolve,reject)=>{
    reject('失败')
})
promise2.then(
    value=>{console.log(value)},
    reason=>{console.log(reason)}  
)
class MyPromise {

   ...前面代码一样

    then(onFulfilled, onRejected) {
        if (this.state === MyPromise.FULFILLED) { 
            onFulfilled(this.value);
          }
        if (this.state === MyPromise.REJECTED) {
            onRejected(this.reason)
        }
    }
}

因为then是在创建实例后再进行调用的,因此我们在constructor外面创建一个then方法,看到上面例子中可以发现原生Promisethen方法是有两个参数的,且都是回调函数的,then中的第二个回调充当了catch一样的效果,在Promise状态变成更为rejected时触发的,只不过后来加了一个catch,因此我们给手写的then里面添加两个参数onFulfilled, onRejected,分别为状态为成功时和拒绝时,并且看到上面例子中只会执行成功状态或失败状态的其中一个,因此我们手写时就要判断状态是什么,再执行相应状态的函数,并且分别为它们传入之前在resolverejrct中保留的值valuereason

3.解决执行异常

情况一

因为原生的Promise考虑到了很多情况,因此我们要改进我们的Promise


let promise = new Promise((resolve, reject) => {
    throw new Error('失败test');
})

promise.then(
    value => { console.log(value) },
    reason => { console.log(reason.message) }  
)

可以看到原生的promsie里面调用then方法时可以把错误的信息输出出来,再来看看我们写的


let promise = new MyPromise((resolve, reject) => {
    throw new Error('失败test');
})

promise.then(
    value => { console.log(value) },
    reason => { console.log(reason.message) }
)

image.png

可以看到报错了

class MyPromise {
    
    
    
       try {
              executor(resolve, reject); 
           }catch (error) {
              reject(error); 
           }
    
       then(){}
}

所以呢我们在执行resolvereject时,进行判断,如果没有报错就正常执行,如果报错就把错误信息传给reject方法,并且执行reject方法,这样就不会出现上面的问题.

情况二


let promise = new Promise((resolve, reject) => {
    resolve('成功')
})

promise.then(
    undefined,  
    reason => { console.log(reason.message) }    
)

可以看到原生的promisethen里面的两个参数如果不是函数的话,是被忽略的,执行没有问题,再来看看我们的


let promise = new MyPromise((resolve, reject) => {
    resolve('成功')
})

promise.then(
    undefined,
    reason => { console.log(reason.message) }
)

image.png

报错了

class MyPromise {

   //...前面代码一样

   then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
        
        if (this.state === MyPromise.FULFILLED) { // 调用then的Promise对象状态已经变更为 fulfilled
            onFulfilled(this.value)
          }
        if (this.state === MyPromise.REJECTED) {
            onRejected(this.reason)
        }
    }
}

所以我们就用三元运算符来判断,如果是函数就把原来的函数赋给它,如果不是函数就把它用函数包着,返回它或把它抛出就可以了。

4.实现异步功能

在对代码进行了一个基本修补后,我们就可以来实现promise的异步功能了,我们来一个看原生的promise代码:


console.log('1')
let promise = new Promise((resolve, reject) => {
    console.log('2')
    resolve('成功')
})
promise.then(
    value => { console.log(value)},
    reason => { console.log(reason)}
)
console.log('3')

image.png


console.log('1')
let promise = new MyPromise((resolve, reject) => {
    console.log('2')
    resolve('成功')
})
promise.then(
    value => { console.log(value)},
    reason => { console.log(reason)}
)
console.log('3')

image.png

class MyPromise {

   

  then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
        if (this.state === MyPromise.FULFILLED) { 
            setTimeout(() => {
                onFulfilled(this.value);
            })
          }
        if (this.state === MyPromise.REJECTED) {
            setTimeout(() => {
                onRejected(this.reason);
            })
        }
    }
}

原生promise是异步的并且是微任务,而我们手写的promise全是同步代码,为了达到这个效果,我们这里方便一点就用setTimeout来模拟这个异步效果,我们在进行if状态判别后给代码添加setTimeout,要不然状态不符合添加异步也是没有意义的。

改完代码的执行结果:

image.png

但是异步的问题我们真的解决了吗?我们接着往下看。

5.实现回调保存

这里将要进入难点部分,我们来一个看原生的promise代码:

console.log('1')
let promise = new Promise((resolve, reject) => {
    console.log('2')
    setTimeout(() => {
       resolve('成功')
       console.log('4')  
    })
})
promise.then(
    value => { console.log(value)},
    reason => { console.log(reason)}
)
console.log('3')

image.png

再来看看我们手写的输出结果

console.log('1')
let promise = new MyPromise((resolve, reject) => {
    console.log('2')
    setTimeout(() => {
        resolve('成功')
        console.log('4')      
    })
})
promise.then(
    value => { console.log(value)},
    reason => { console.log(reason)}
)
console.log('3')

image.png

诶,没有打印resolve的结果对吧,我们先来捋一捋原生promise执行过程

  • console.log(‘1’)–输出1
  • console.log(‘2’)–输出2
  • setTimeout()放入宏任务队列
  • promise.then()放入微任务队列
  • console.log(‘3’)–输出3
  • 执行微任务,发现resolve()没执行,promise状态没有改变,还是pending状态,那么就不执行
  • 执行宏任务,resolve()把状态变为fulfilled,执行console.log(‘4’)–输出4
  • 最后执行.then–输出成功

再来捋一捋手写的promise,没有输出成功的原因是当我们执行到then方法时,我们then方法是根据条件来执行代码的,也就是说没有符合的情况也就是没有符合的状态,也就是没有情况对应pending状态对吧,总的来说就是我们的then方法没有能resolve执行完状态改变后再执行自己的能力,那改进吧

class MyPromise {
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(executor) {
        this.state = MyPromise.PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledCallbacks = []
        this.onRejectedCallbacks = []
    
        const resolve = (value) => { 
          if (this.state === MyPromise.PENDING) { // fulfilled状态的上一种状态只能是 pending,状态一经变更就不在逆转
            this.state = MyPromise.FULFILLED
            this.value = value
            this.onFulfilledCallbacks.forEach(callback => callback(value))
          }
        }
    
        const reject = (reason) => {
          if (this.state === MyPromise.PENDING) {  // 同上
            this.state = MyPromise.REJECTED
            this.reason = reason
            this.onRejectedCallbacks.forEach(callback => callback(reason))
          }
        }
        try {
            executor(resolve, reject)
        }catch (error) {
            reject(error)
        }
    }

    then(onFulfilled, onRejected) {

        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

        if (this.state === MyPromise.FULFILLED) { // 调用then的Promise对象状态已经变更为 fulfilled
            setTimeout(() => {
                onFulfilled(this.value)
            })
          }
        if (this.state === MyPromise.REJECTED) {
            setTimeout(() => {
                onRejected(this.reason)
            })
        }
        if(this.state === MyPromise.PENDING){ 
            this.onFulfilledCallbacks.push(onFulfilled)
            this.onRejectedCallbacks.push(onRejected)
        }
    }
}

我们在then里面增加一种情况,当thenresolvereject执行前被执行时也就是pending状态,我们把then方法中对应的回调函数放入对应数组中,我们再上面再定义两个数组分别为onFulfilledCallbacksonRejectedCallbacks,为什么是数组呢,因为可能.then后面又接.then,状态都为pending,然后我们把它们放入resolvereject中,当resolvereject执行时,去遍历调掉数组里面的回调函数是不是实现了上面我们想要的效果,只能说秒呀,总结就是pending状态时把回调放到resolvereject中去执行

我们来看执行结果:

image.png

诶,先输出成功,这是因为resolve里面都是同步代码所有先执行了resolve(),所以我们要想办法把resolve里面异步执行数组里面的函数就可以解决这个问题

class MyPromise {

  

 then(onFulfilled, onRejected) {

        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

        if (this.state === MyPromise.FULFILLED) { 
            setTimeout(() => {
                onFulfilled(this.value);
            })
          }
        if (this.state === MyPromise.REJECTED) {
            setTimeout(() => {
                onRejected(this.reason);
            })
        }
        if(this.state === MyPromise.PENDING){ 
            this.onFulfilledCallbacks.push(value => {
                setTimeout(() => {
                    onFulfilled(value);
                })
            })
            this.onRejectedCallbacks.push(reason=>{
                setTimeout(()=>{
                    onRejected(reason);
                })
            })
        }
    }
}

我们通过在把回调函数放入数组时,把它放进一个setTimeout里面,那是不是数组里面的方法都变成异步了,当resolve执行数组里面的函数就是异步了,那么不就实现了吗,真正实现了和原生promise一样的效果,resolve执行完毕后把状态变为fulfilledrejected才执行.then

我们来看效果:

image.png
完美

6.实现链式效果

来到我们最后一步,完成.then的链式功能,也就.then后面接.then

class MyPromise {

   //... 省略前面的代码

  then(onFulfilled, onRejected) {
        // 判断传入 then 中的参数是否为函数类型,如果是那顺利执行,否则我们人为写入一个函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
    
        // then的执行结果要返回一个新的promise
        return new MyPromise((resolve, reject) => {
          if (this.state === MyPromise.FULFILLED) { // 调用then的Promise对象状态已经变更为 fulfilled
            setTimeout(() => {
                const result = onFulfilled(this.value)
                resolve(result)
            })
          }
          if (this.state === MyPromise.REJECTED) {
            setTimeout(() => {
                const result = onRejected(this.reason)
                reslove(result)
            })
          }
          if (this.state === MyPromise.PENDING) {  // 调用then的Promise对象状态没有变更,则缓存then中的回调
            this.onFulfilledCallbacks.push(value => {
              setTimeout(() => {
                  const result = onFulfilled(value)
                  resolve(result)
              })
            })
            this.onRejectedCallbacks.push(reason => {
              setTimeout(() => {
                  const result = onRejected(reason)
                  reslove(result)
              })
            })
          }
        })
      }
}

要想后面.then能接.then,那then方法里面得返回一个promise实例吧,因为原生的then里面return出来一个值,会当作下一个then里面回调函数里面的参数,所以我们const result = onFulfilled(this.value);把这次的then执行掉,并接收这次then里面回调函数里面return出来的值,再resolve(result);把下一个then的状态改为fulfilled,把下次then的回调函数里面的参数保存好,reject是一样的,因为我们每次都是创建一个新的promise对象,每次数组都是不一样的,所有不用担心。

console.log('1')
let promise = new MyPromise((resolve, reject) => {
    console.log('2')
    setTimeout(() => {
        resolve('成功')
        console.log('4')      
    })
})
promise.then(value => { 
    console.log(value)
    return 'hello'
}).then(value=>{
    console.log(value)
})
console.log('3')

用我们的promise来执行这段代码

来看最终效果吧:

image.png

ok,大功告成呀!
这里还有一份更完善的代码,大体思路是不变的,只是加了点try,catch,你就可以.then里面抛出错误了。

    class MyPromise {
        static PENDING = 'pending'
        static FULFILLED = 'fulfilled'
        static REJECTED = 'rejected'
        constructor(executor) {
            this.state = MyPromise.PENDING
            this.value = undefined
            this.reason = undefined
            this.onFulfilledCallbacks = []
            this.onRejectedCallbacks = []
        
            const resolve = (value) => { 
              if (this.state === MyPromise.PENDING) { // fulfilled状态的上一种状态只能是 pending,状态一经变更就不在逆转
                this.state = MyPromise.FULFILLED
                this.value = value
                this.onFulfilledCallbacks.forEach(callback => callback(value))
              }
            }
        
            const reject = (reason) => {
              if (this.state === MyPromise.PENDING) {  // 同上
                this.state = MyPromise.REJECTED
                this.reason = reason
                this.onRejectedCallbacks.forEach(callback => callback(reason))
              }
            }
            
            try {
                executor(resolve, reject)
            } catch (e) {
                reject(e)
            }
          }

          then(onFulfilled, onRejected) {
            // 判断传入 then 中的参数是否为函数类型,如果是那顺利执行,否则我们人为写入一个函数
            onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
            onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
        
            // then的执行结果要返回一个新的promise
            return new MyPromise((resolve, reject) => {
              if (this.state === MyPromise.FULFILLED) { // 调用then的Promise对象状态已经变更为 fulfilled
                setTimeout(() => {
                  try{
                    const result = onFulfilled(this.value)
                    resolve(result)
                  }catch(error){
                      reject(error)
                  }
                })
              }
              if (this.state === MyPromise.REJECTED) {
                setTimeout(() => {
                  try{
                    const result = onRejected(this.reason)
                    resolve(result)
                  }catch(error){
                      reject(error)
                  }
                })
              }
              if (this.state === MyPromise.PENDING) {  // 调用then的Promise对象状态没有变更,则缓存then中的回调
                this.onFulfilledCallbacks.push(value => {
                  setTimeout(() => {
                    try{
                      const result = onFulfilled(value)
                      resolve(result)
                    }catch(error){
                      reject(error)
                    }
                  })
                })
                this.onRejectedCallbacks.push(reason => {
                  setTimeout(() => {
                    try{
                      const result = onRejected(reason)
                      reject(result)
                    }catch(error){
                      reject(error)
                    }
                  })
                })
              }
            })
          }
        
      }

例子:

console.log('1')
let promise = new MyPromise((resolve, reject) => {
    console.log('2')
    setTimeout(() => {
        resolve('成功')
        console.log('4')      
    })
})
promise.then(value => { 
    console.log(value)
    return 'hello'
}).then(value=>{
    console.log(value)
    throw new Error('失败')
}).then(
  value=>console.log(value),
  reason=>console.log(reason.message)   
)
console.log('3')

执行结果:

image.png

最后提一嘴,还有一种场景就是then里面返回了一个new Promise,聪明的你肯定能想到如何解决

总结

本文到这里就结束了,希望对你手写Promise有帮助,如有错误,疏漏的地方恳请指出,感谢你的阅读!

image.png

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=20187,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?