手写promise,面试无敌

11 篇文章 0 订阅
1 篇文章 0 订阅

我们面试的时候经常会问到Promise的使用;有的面试官再深入一点,会继续问是否了解Promise的实现方式,或者有没有阅读过Promise的源码;今天我们就来看一下,Promise在内部是如何实现来链式调用的。

首先,我们先回顾一下promise的基本使用:

    let res = new Promise((resolve) => {
      setTimeout(() => {
        resolve("成功了");
      }, 1000);
    });
    res.then((data) => {
        // 过一秒后打印出 => 成功了
      console.log("data:", data);
    });
  • 从这儿我们能够看出来

  • 1、Promise是通过构造函数实例化一个对象,然后通过实例对象上的then方法,来处理异步返回的结果,并且他接受一个函数(该函数接受两个方法作为参数=>resolve,reject)作为参数,

  • 2、promise内部会保存失败和成功的结果,即resolve和reject的结果

  • 3、promise对象内部有一个resolve方法和一个resolve方法

  • 4、promise的对象上面有一个then方法

  • Promise A+ 规范也说

  • 1、每一个promise对象初始化的时候,他的状态一定是pending

  • 2、一个promise对象对应着三种状态 pending, fulfilled, rejected

  • 3、promise对象的状态如果是成功或者失败的状态,就不能在变为其它的状态,如果状态为pending,可以继续改变状态为成功或者失败
    在这里插入图片描述

所以我们定义一个基本的promise

//先定义三种状态
const fulfilled = "fulfilled"; //成功
const rejected = "rejected"; // 失败
const pending = "pending"; // 进行中

class Promise {
  constructor(executor) {
    //每一个promise对象,最开始的状态肯定是pending
    this.status = pending;
    this.value = ""; //存储成功的原因
    this.reason = ""; //存储失败的原因

    // 调用resolve 方法会把当前promise对象的状态变为fulfilled,并且会执行then方法的回调
    let resolve = (value) => {
      if (this.status === pending) {
        this.status = fulfilled; //改变状态
        this.value = value; // 保存成功的值
      }
    };
    // 调用reject 方法会把当前promise对象的状态变为rejected,并且会执行catch方法的回调
    let reject = (reason) => {
      if (this.status === pending) {
        this.status = rejected; // 改变状态
        this.reason = reason; // 保存失败的值
      }
    };
  }
  // then方法会接受两个参数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
  then(onFulfilled, onRejected) {}
}

其次, 我们传入promise构造函数的execute是一个立即执行函数,我们可以试一下:

   new Promise((res) => {
      console.log("立即执行"); // 会立即打印出结果
    });

所以我们需要在构造函数中执行一下这个立即执行函数:

    // 立即执行,为了防止执行报错,我们tryCatch一下,如果报错了,就把promise的状态改变为失败
    try {
      executor(resolve, reject);
    } catch (e) {
      if (this.status === pending) {
        this.status = rejected;
        this.reason = e;
      }
    }

then方法的实现:
当Promise的状态改变之后,不管成功还是失败,都会触发then回调函数。因此,then的实现也很简单,就是根据状态的不同,来调用不同处理终值的函数。

再来回顾一下,我们调用了execute中的resolve或者reject的时候,对应的then返回中的参数函数才会执行,说明
在执行了resolve和reject之后,响应的then方法中的函数才会去执行

但是当then里面函数运行时,resolve由于可能是异步执行的,还没有来得及修改state,此时还是PENDING状态;因此我们需要对异步的情况做一下处理。
使用两个数组去分别保存失败和成功的回调函数

constructor(executor) {
    this.successFn = []; // 保存成功的回调
    this.failFn = []; //保存失败的回调
  }

所以then方法实现如下:

  // then方法会接受两个参数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
  then(onFulfilled, onRejected) {
    //参数的可选
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (val) => val;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (err) => {
            throw err;
          };

    // 由于我们调用then方法的时候,promise对象的状态可能还是pending状态(异步的情况),这个时候我们就先要保存他的成功或者失败的回调,然后等状态改变了再去调用
    if (this.status === pending) {
      this.successFn.push(onFulfilled);
      this.failFn.push(onRejected);
    }
    //如果已经成功了
    if (this.status === fulfilled) {
      onFulfilled(this.value);
    }
    //如果已经失败了
    if (this.status === rejected) {
      onRejected(this.reason);
    }
  }

对应的构造函数中增加两个数组:

  constructor(executor) {
    //每一个promise对象,最开始的状态肯定是pending
    this.status = pending;
    this.value = ""; //存储成功的原因
    this.reason = ""; //存储失败的原因

    this.successFn = []; // 保存成功的回调
    this.failFn = []; //保存失败的回调
  }

然后resolve和reject函数也做相应的修改

    let resolve = (value) => {
      if (this.status === pending) {
        this.status = fulfilled; //改变状态
        this.value = value; // 保存成功的值
        //改变状态了 ,去执行then方法中成功的回调函数
        if (this.successFn.length > 0) {
          this.successFn.forEach((fn) => fn(this.value));
        }
      }
    };
    // 调用reject 方法会把当前promise对象的状态变为rejected,并且会执行catch方法的回调
    let reject = (reason) => {
      if (this.status === pending) {
        this.status = rejected; // 改变状态
        this.reason = reason; // 保存失败的值
        // 执行失败的回调
        if (this.failFn.length > 0) {
          this.failFn.forEach((fn) => fn(this.reason));
        }
      }
    };

到这里一个简易版的promise就实现了,这个promise支持简单的使用,支持异步,但是现在还不支持链式调用
贴一下代码:

//先定义三种状态
const fulfilled = "fulfilled"; //成功
const rejected = "rejected"; // 失败
const pending = "pending"; // 进行中

class Promise {
  constructor(executor) {
    //每一个promise对象,最开始的状态肯定是pending
    this.status = pending;
    this.value = ""; //存储成功的原因
    this.reason = ""; //存储失败的原因

    this.successFn = []; // 保存成功的回调
    this.failFn = []; //保存失败的回调

    // 调用resolve 方法会把当前promise对象的状态变为fulfilled,并且会执行then方法的回调
    let resolve = (value) => {
      if (this.status === pending) {
        this.status = fulfilled; //改变状态
        this.value = value; // 保存成功的值
        //改变状态了 ,去执行then方法中成功的回调函数
        if (this.successFn.length > 0) {
          this.successFn.forEach((fn) => fn(this.value));
        }
      }
    };
    // 调用reject 方法会把当前promise对象的状态变为rejected,并且会执行catch方法的回调
    let reject = (reason) => {
      if (this.status === pending) {
        this.status = rejected; // 改变状态
        this.reason = reason; // 保存失败的值
        if (this.failFn.length > 0) {
          this.failFn.forEach((fn) => fn(this.reason));
        }
      }
    };
    // 立即执行,为了防止报错,我们tryCatch一下,如果报错了,就把promise的状态改变为失败
    try {
      executor(resolve, reject);
    } catch (e) {
      if (this.status === pending) {
        this.status = rejected;
        this.reason = e;
      }
    }
  }
  // then方法会接受两个参数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
  then(onFulfilled, onRejected) {
    //参数的可选
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (val) => val;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (err) => {
            throw err;
          };

    // 由于我们调用then方法的时候,promise对象的状态可能还是pending状态,这个时候我们就先要保存他的成功或者失败的回调,然后等状态改变了再去调用
    if (this.status === pending) {
      this.successFn.push(onFulfilled);
      this.failFn.push(onRejected);
    }
    //如果已经成功了
    if (this.status === fulfilled) {
      onFulfilled(this.value);
    }
    //如果已经失败了
    if (this.status === rejected) {
      onRejected(this.reason);
    }
  }
}

export default Promise;

接下来,我们看看如何实现promise 的链式调用
其实在promise中,链式调用才是promise的核心,我们对照promise/A+规范,一步一步地来实现,我们先来看一下规范是如何来定义的:
在这里插入图片描述
上图中主要说明这么几个点:

  • 1、then方法必须返回一个新的promise对象
  • 2、如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
  • 3、如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
  • 4、如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
  • 5、如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因

也就是说,每个then方法都要返回一个新的Promise对象,这样我们的then方法才能不断的链式调用;我们对上面的代码进行简单的改写,不论then进行什么操作,都返回一个新的Promise对象:


then(){
    let promise2 = new Promise((resolve, reject) => {

    })
    return promise2
}

从这个代码中我们可以看到,then方法中会返回一个新的promise对象,我们暂且称他为promise2
在这里插入图片描述
上图所选中的地方说明x和promise2不能是同一个对象,不然需要抛出一个类型错误
下面,我们来改写一下then方法:

    // then方法会接受两个参数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
    then(onFulfilled, onRejected) {
        //参数的可选
        onFulfilled =
            typeof onFulfilled === "function" ? onFulfilled : (val) => val;
        onRejected =
            typeof onRejected === "function"
                ? onRejected
                : (err) => {
                    throw err;
                };
        let promise2 = new Promise((resolve, reject) => {
            // 由于我们调用then方法的时候,promise对象的状态可能还是pending状态,这个时候我们就先要保存他的成功或者失败的回调,然后等状态改变了再去调用
            if (this.status === pending) {
                onFulfilled && this.successFn.push(() => {
                    try {
                        let x = onFulfilled(this.value);
                        // x这个返回值也有可能是一个promise对象,所以需要resolvePromise这个处理程序去进行比对一下
                        // 判断x和promise2是不是同一个promise
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }

                })
                onRejected && this.failFn.push(() => {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                })
            }

            //如果已经成功了
            if (this.status === fulfilled) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                }, 0)
            }
            //如果已经失败了
            if (this.status === rejected) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }

                }, 0)
            }
        })

        return promise2
    }

这个resolvePromise就是promise A+ 规范中说的处理程序,针对这个处理程序,规范中主要是说了以下这几个点:

  • 1、x 与 promise 相等
    1)如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
    2)x 为 Promise:
    如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
    如果 x 处于执行态,用相同的值执行 promise
    如果 x 处于拒绝态,用相同的据因拒绝 promise

  • 2:x 为对象或函数
    把 x.then 赋值给 then
    如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
    如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
    如果 resolvePromise 以值 y 为参数被调用,则运行 [Resolve]
    如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
    如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
    如果 then 不是函数,以 x 为参数执行 promise
    如果 x 不为对象或者函数,以 x 为参数执行 promise

下面我们来完成这个resolvePromise :

 function resolvePromise(promise2, x, resolve, reject) {
     if (promise2 === x) {
         // 如果promise2 和x 相等,那么抛出类型错误
         throw new Error('类型错误')
     }
     //判断x是一个普通值还是一个promise
     if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
         // console.log('x:',  x.then());
         try {
             let then = x.then;
             //说明x是一个promise
             if (then && typeof then === 'function') {
                 //执行then方法,this指向X ,成功的回调函数有可能又是一个promise
                 then.call(x, (y) => {
                     // y 有可能也是一个promise对象,所以接着处理
                     resolvePromise(promise2, y, resolve, reject)
                 }, (r) => {
                     reject(r)
                 })
             } else {
                 resolve(x)
             }
         } catch (e) {
             reject(e)
         }
     } else {
         resolve(x)
     }
 }

这样子我们的链式调用就可以跑起来了,如下:

    let x = new MyPromise((res, rej) => {
      res(12312)
    });
    x.then(
      (data) => {
        console.log("data:", data);
        return data
      },
      (err) => {
        console.log("err:", err);
      }
    ).then(res => {
      console.log('第二个then:',res) // => 12312
    })

当然,日常我们使用的promise,还有很多扩展的方法,像resolve,reject,finally,race,all ,catch等方法,下面我们也来一一的实现一下:
1、resolve方法:
定义:其实就是返回一个成功状态的promise

     static resolve(value) {
         return new Promise((res, rej) => {
             // 直接调用resolve,将状态变为成功态
             res(value)
         })
     }

例子:

    let x = MyPromise.resolve(111)
    x.then(res =>console.log(res))  // 打印出  111

2、rejected方法:
定义:其实就是返回一个失败状态的promise

     static reject(err) {
         return new Promise((res, rej) => {
               // 直接调用rejext,将状态变为失败态
             rej(err)
         })
     }

3、finally方法
定义:finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作`(摘自es6)

     finally(callback) {
         callback() // 直接执行
         // 调用then方法
         return this.then(res => {
             return this.resolve(res)
         }, err => {
             return this.reject(err)
         })
     }

4、race方法:
定义:Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数(摘自es6)。

        static race(args) {
            return new Promise((resolve,reject) =>{
                    for (let i = 0; i < args.length; i++) {
                    if (isPromise(args[i])) {
                        args[i].then(res=>{
                            // 任何一个promise的状态改变了,我们就把外层的promise状态改变,就只会得到第一个状态改变了的promise的返回值
                            resolve(res)
                        },err =>{
                            reject(err)
                        })
                    } else {
                        //如果不是promise 直接成功
                        resolve(args[i])
                    }
                }
            })
        }
//判断是否是promise
 function isPromise(fn) {
     if ((typeof fn === 'object' && fn !== null) || typeof fn === 'function') {
         return fn.then && typeof fn.then === 'function'
     }
     return false
 }

例子:

 let x3 = new MyPromise(res => {
            setTimeout(() => {
                res(111)
            }, 1000)
        })
        let x4 = new Promise(res => {
            setTimeout(() => {
                res(222)
            }, 2000)
        })
        let a = Promise.race([x3, x4, 33])
        a.then(dd => {
            console.log('dd:', dd) // 打印出 33  如果上面两个promise不加定时器的话,那么应该打印出111
        })

5、all 方法:
定义:Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例(摘自es6)。

 //promise的all方法
 Promise.all = function(promises){
     // 返回一个新的promise
     return new Promise((resolve,reject)=>{
         let arr = [];//返回的数组
         let i = 0 ;
         let processData = (index,data) =>{
             arr[index]= data;
             if(++i === promises.length ){
                 // 如果当前i等于传入数组的长度,那么说明所有的promise项都已经执行完成了
                 resolve(arr)
             }
         }
 
         for(let i = 0 ; i< promises.length ; i++){
             let current = promises[i];//获得每一项
             if(isPromise(current)){
                 //如果是promise,那么等他的then方法执行的时候将结果放入到新的数组中去
                 current.then(data =>{
                     // 按照传入数组的位置将结果放入到新的数组中
                     processData(i,data)
                 },reject)
             }else {
                 processData(i,current)
             }
         }
     })
 }

6、catch方法:
定义:Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数(摘自es6)。

     catch(err) {
         return this.then(null, err)
     }
  • 5
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值