likes
comments
collection
share

手写一个面试中经常被问到的promise,一看就会(简单版)

作者站长头像
站长
· 阅读数 20

小二,上酒!

天涯的渡口, 一人陪马儿清瘦, 多么富有, 天下了心中。


介绍

promise相信大家在开发中都使用过,面试中如果提到ES6必问,初衷是用来解决回调地狱的,在开发中更常用async和await,他们也可以相结合使用,promise有一个很美的中文解释叫《期约》。

基本用法

new Promise()

Promise 构造方法接收一个回调方法,会在内部调用,然后回传回两个回调方法 promise 有三个状态 pending(初始状态) fulfilled(成功) rejected(失败)

    const p = new Promise((resolve,reject) => {
        //这里写一些业务逻辑
        //成功的回调 修改当前实例状态为fulfilled
        resolve(1);
        //失败的回调 修改当前实例状态为rejected
        reject(1)
    })

then()

then方法接收2个回调函数,第一个是成功的回调,第二个是失败的回调。 如果promise的状态为fulfilled则第一个回调被执行,否则第二个被执行。

    p.then(res => {
        console.log(res);
    },reason => {
        console.log(reason)
    })

开搞

我们就来实现基本的promise、then方法和all方法(在实现之前会介绍)

MyPromise类

    //先定义三个状态常量,这样可以避免在写代码期间维护字符串
    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected'

    class MyPromise {
      //定义初始状态
      status = PENDING;
      //用来保存成功的值
      value = null;
      //用来保存失败的值
      reason = null;
      
      //构造器接收一个执行器方法,会在构造器内部立即执行,然后回传两个回调方法
      constructor(executor){
          executor(this.resolve,this.reject)
      }
      
      //成功的回调
      resolve = (val) => {
          //这里加判断的原因是因为promise的状态只会被修改一次,成功即成功,失败即失败
          if(this.status != PENDING){
              return;
          }
          this.status = FULFILLED;
          this.value = val;
      }
      
      //失败的回调
      reject = (reason) => {
          if(this.status != PENDING){
              return;
          }
          this.status = REJECTED;
          this.reason = reason;
      }
    }

来测试一下

    const p = new MyPromise((resolve,reject) => {
        resolve(123)
    })
    console.log(p)

下图可以看到promise的状态被修改为成功,而值就是我们传的123 手写一个面试中经常被问到的promise,一看就会(简单版) 截图贴进来太麻烦了,我们来实现then方法吧

then()

上面介绍过then方法接收两个回调函数为参数,当成功的时候执行第一个,失败反之 明白了原理我们就开搞

    //把下面代码粘到class中去
    
    then(onFulfilled,onRjected){
        //当前状态为成功的话就去回调第一个方法
        if(this.status === FULFILLED){
            //回传的值是当前成功的值  resolve()传的
            onFulfilled(this.value)
        }else if(this.status === REJECTED){
            //回传的值是当前失败的值  reject()传的
            onRjected(this.reason)
        }
    }

来测试一下

    p.then(res => {
        console.log('成功的值是',res)
    },reason => {
        console.log('失败的值是',reason)
    })
    //控制台打印  成功的值是123

当前看着结果是没什么问题,但是如果我们resolve被包在了异步里面的话就会导致then方法先执行,执行时当前promise还是PENDING状态,成功和失败的回调都不会被执行 例1:

    const p = new MyPromise((resolve,reject) => {
        //设置一个定时器,1秒之后修改promise状态
        setTimeout(() => {
            resolve(123)
        }, 1000)
    })
    
    p.then(res => {
        console.log(res)
    },reason => {
        console.log(reason)
    })
    //控制台什么都没有打印,因为错过了判断

来修改一下then\resolve\reject方法,修改后代码如下

    //先定义三个状态常量,这样可以避免在写代码期间维护字符串
    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected'
    class MyPromise {
      //定义初始状态
      status = PENDING;
      //用来保存成功的值
      value = null;
      //用来保存失败的值
      reason = null;
      
      //----------新增------------
      //成功的回调方法暂存
      onFulfilledCb = [];
      //失败的回调方法暂存
      onRjectedCb = [];
      
      //构造器接收一个执行器方法,会在构造器内部立即执行,然后回传两个回调方法
      constructor(executor){
          executor(this.resolve,this.reject)
      }
      
      //成功的回调
      resolve = (val) => {
          //这里加判断的原因是因为promise的状态只会被修改一次,成功即成功,失败即失败
          if(this.status != PENDING){
              return;
          }
          this.status = FULFILLED;
          this.value = val;
          //----------新增------------
          //如果成功的回调数组中有待执行的方法,取出来执行
          if(this.onFulfilledCb.length){
            this.onFulfilledCb.forEach(fn => {
              fn(val)
            })
          }
      }
      
      //失败的回调
      reject = (reason) => {
          if(this.status != PENDING){
              return;
          }
          this.status = REJECTED;
          this.reason = reason;
          //----------新增------------
          //如果失败的回调数组中有待执行的方法,取出来执行
          if(this.onRjectedCb.length){
            this.onRjectedCb.forEach(fn => {
              fn(reason)
            })
          }
      }

      then(onFulfilled,onRjected){
        //当前状态为成功的话就去回调第一个方法
        if(this.status === FULFILLED){
          //回传的值是当前成功的值  resolve()传的
          onFulfilled(this.value)
        }else if(this.status === REJECTED){
          //回传的值是当前失败的值  reject()传的
          onRjected(this.reason)
        }else if(this.status === PENDING){//----------新增------------
          this.onFulfilledCb.push(onFulfilled)
          this.onRjectedCb.push(onRjected)
        }
      }
    }
    
    //再执行上面例1,控制台1秒后输出123
   

其实then方法是可以链式调用的 比如这样

    const p = new Promise((resolve,reject) => {
        resolve(123)
    })
    p.then(res => {
        console.log(res)
        return 456
    },reason => {
        console.log(reason)
    }).then(res => {
        console.log(res)
    },reason => {
        console.log(reason)
    })
    //控制台输出123和456

我们来修改一下自己的then方法

    then(onFulfilled,onRjected){
        return new MyPromise((resolve,reject) => {
            //当前状态为成功的话就去回调第一个方法
            if(this.status === FULFILLED){
              //回传的值是当前成功的值  resolve()传的
              //---------修改----------
              handleThen(onFulfilled(this.value),resolve,reject)
            }else if(this.status === REJECTED){
              //回传的值是当前失败的值  reject()传的
              //---------修改----------
              handleThen(onRjected(this.reason),resolve,reject)
            }else if(this.status === PENDING){//----------新增------------
              this.onFulfilledCb.push(onFulfilled)
              this.onRjectedCb.push(onRjected)
            }
        })
     }
     
     //再来一个处理then方法的方法  写在class外面就可以
     function handleThen(val,resolve,reject){  //val是回调方法的返回值,res成功的回调,ren失败的回调
         //我们知道then方法能返回普通值也能继续返回一个promise,这个promise的执行结果就是下一个.then的实例
         //如果val又是一个promise的话  那它一定有then方法
         if(val instanceof MyPromise){
             val.then(res => {
                 resolve(res)
             },reason => {
                 reject(reason)
             })
         }else{//如果不是promise直接调用成功的回调就可以
             resolve(val)
         }
     }
     
     //测试一下
     const p = new MyPromise((resolve,reject) => {
        resolve(123)
    })
    p.then(res => {
        console.log(res)
        return 456
    },reason => {
        console.log(reason)
    }).then(res => {
        console.log(res)
    },reason => {
        console.log(reason)
    })
     //控制台同样输出123 456

all()

all方法接收一个promise数组为参数,返回一个promise实例,如果数组中所有promise都为成功状态,则all方法返回成功,若有一个失败则返回失败 all方法的用法如下

    const p1 = new Promise((resolve,reject) => {
        resolve(1)
    })
    const p2 = new Promise((resolve,reject) => {
        resolve(2)
    })
    const p3 = new Promise((resolve,reject) => {
        resolve(3)
    })
    const allP = Promise.all([p1,p2,p3])
    allP.then(res => {
        console.log(res)
    },reason => {
        console.log(reason)
    })
    //控制台打印 [1,2,3]

接下来我们来实现一下自己的all方法

    //调用all方法时是类名打点调用改的, 所以写成类的静态方法
    static all(pArr){
        //计数
        let num = 0;
        //结果数组
        let resultArr = [];
        return new MyPromise((resolve,reject) => {
            pArr.forEach((p,i) => {
                p.then(res => {
                    //进入第一个回调说明当前实例是成功的  num++
                    num++;
                    resultArr[i] = res;
                    //如果num >= 传进来的promise实例  说明所有promise都是成功状态
                    if(num >= pArr.length){
                        resolve(resultArr)
                    }
                },reason => {
                    reject(reason)
                })
            })
        })
    }
    //测试一下
    const p1 = new MyPromise((resolve,reject) => {
        resolve(1)
    })
    const p2 = new MyPromise((resolve,reject) => {
        resolve(2)
    })
    const p3 = new MyPromise((resolve,reject) => {
        resolve(3)
    })
    const allP = MyPromise.all([p1,p2,p3])
    allP.then(res => {
        console.log(res)
    },reason => {
        console.log(reason)
    })
    //控制台同样打印出 [1,2,3]

期约

说一说一开始提到的promise的中文翻译《期约》,这个翻译真的太到位了,并且我觉得非常好听 期约:我在今日与你期许一个约定(resolve,reject),你会在未来拥抱我(then).

总结

以上只是一个promise简单的实现, 我没有看过a+规范,但相信这些应该可以应付面试了,还有好多地方没有写全,比如对错误的处理,还有all方法里面再涉及到异步等... 一门技术我觉得了解它的用法以及主要运行原理就够了,并不是非要手撸出来,卷来卷去卷的是我们自己,金字塔尖上能站多少人呢?

上面代码没有做类型判断,理解和会用就好,我又偷个懒,哈哈~

做人和写代码我都不太喜欢复杂的东西,总想着能不能简单点。