学习如何手撸类似promise类---DIYPromise类(未完)

本文深入解析Promise的工作原理,包括状态管理、链式调用、then方法实现及微任务处理。探讨了resolve、reject、then、catch和finally等关键方法的内部机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

第一步:列出三大块:this.then, resolve/reject fn(resolve/reject)

第二步:this.then负责注册所有的函数,resolve/reject负责执行所有的函数

第三步:在resolve/reject里面加上setTimeout 防止还没进行then注册,就直接执行resolve了

第四步:resolve/reject里面要返回this, 这样就可以链式调用了

第五步:三个状态的管理:

              pending 初始化状态

              resolve 成功

              reject  失败

              promise的链式调用 在then里面return一个promise 这样才能then里面加上异步函数

              加上catch

/*
DIYPromisee类必须接收一个函数参数(也就是需要执行异步任务的函数),该函数将在传入之后立即执行,
并传入DIYPromise对象下的两个方法resolve和reject
*/


class DIYPromise {
  /*
    DIYPromise状态有三种:
    pending => 初始状态
    resolve =>成功
    reject => 失败
    每一个DIYPromise对象只能由pending状态变为resolve或者reject状态,且状态一旦发生改变以后就不能再改变了***
   */
  static PENDING = "PENDING";
  static RESOLVE = "RESOLVE";
  static REJECT = "REJECT";


  constructor ( handler ) {
    if (typeof handler !== 'function') {
      throw new TypeError('Promise resolver undefined is not a function');
    }
    this.status = DIYPromise.PENDING;
    this.resolvedQueues = [];
    this.rejectedQueues = [];
    handler(this._resolve.bind(this), this._reject.bind(this));
  }

  _resolve () {
    /*
      将当前promise的状态由pending变为resolve状态,并执行成功后的注册函数
      如果当前状态改变过了,则return
     */
    console.log('resolve');
    if (this.status !== DIYPromise.PENDING) return
    this.status = DIYPromise.RESOLVE;
    let handler;
    while (handler = this.resolvedQueues.shift()) {
      handler();
    }
  }

  _reject () {
    /*
      将当前promise的状态由pending变为reject状态,并执行失败后的注册函数
      如果当前状态改变过了,则return
     */
    console.log('reject');
    if (this.status !== DIYPromise.PENDING) return
    this.status = DIYPromise.REJECT;
    let handler;
    while (handler = this.rejectedQueues.shift()) {
      handler();
    }
  }

  then (resolvedHandler, rejectedHandler) {
    // 这里传进来的函数不能直接调用,而应该类似推入事件队列里面去
    // 理解事件原理
    console.log('then');
    this.resolvedQueues.push(resolvedHandler);
    this.rejectedQueues.push(rejectedHandler);
  }
}

/*
截止到目前为止,存在一个问题,
那就是: 如果回调函数里面不是一个定时器,情况是是怎么样呢?
结果是 ==> then()里面的回调没有执行,那么为什么then里面的回调没有执行呢?
原因就是:
这个时候,demo1.then()和resolve()是同步的状态,如何保证是异步的?如何解决这个问题?
let demo1 = new DIYPromise((resolve, reject) => {
  console.log('DIYPromise');
  resolve();
});
demo1.then(res => {
  console.log('res', res );
}, error => {
  console.log('出错了', error);
});
 */

那么如何解决上面所说的问题呢?

解决方法:

1、可以在resolve和reject中使用setTimeout();

 _resolve () {
    /*
      将当前promise的状态由pending变为resolve状态,并执行成功后的注册函数
      如果当前状态改变过了,则return
     */
    setTimeout(() => {
      if (this.status !== DIYPromise.PENDING) return
      this.status = DIYPromise.RESOLVE;
      let handler;
      while (handler = this.resolvedQueues.shift()) {
        handler();
      }
    }, 0);
  }

_reject () {
    /*
      将当前promise的状态由pending变为reject状态,并执行失败后的注册函数
      如果当前状态改变过了,则return
     */
    setTimeout(() => {
      if (this.status !== DIYPromise.PENDING) return
      this.status = DIYPromise.REJECT;
      let handler;
      while (handler = this.rejectedQueues.shift()) {
        handler();
      }
    }, 0);
  }

为什么使用这个方法可以解决呢?这就涉及到同步任务和异步任务的概念,以及微任务和宏任务

不管定时器写的是多少时间,它都是一个异步任务,那么异步任务肯定要比同步任务执行要再靠后

但是这种方式还是存在问题:看下面一段代码理解一下:如果使用原生的Promise会是怎么样的结果呢?

setTimeout(() => {
      console.log('setTimeout');
    }, 0);
    Promise.resolve().then(res => {
      console.log('then');
    });
    //那么上面代码是先答应setTimeout还是then呢?

如果使用我们当前的自己写的promise对象的话,又是什么结果呢?

setTimeout(() => {
      console.log('setTimeout');
    }, 0);
    new DIYPromise((resolve) => {
      resolve()
    }).then(res => {
      console.log('then');
    });

结果显然,跟原生promise中的结果是不一样的,为什么会这样呢?

原因是,我们当前写的DIYPromise类中的resolve使用的也是setTimeout,那么都是宏任务,那么这个时候,外层的setTimeout函数比resolve中的setTimeout要先注册,那怎么样才能做到像原生那样,能够让then先执行的这种机制呢?这就是微任务。

那么到底什么是宏任务,什么是微任务呢?

原因是js是单线程的,当我们的代码执行的时候,它会把代码放到主线程中去执行,然后把异步任务代码放到异步线程当中去处理,同步代码执行完之后,会进入到一个eventLoop里面去,事件队列里面是不是有函数,事件队列里面分两个,一个叫宏任务队列,一个叫微任务队列,微任务队列的优先级高于宏任务,如果同时存在宏任务和微任务的话,会优先执行微任务里面的任务,然后才执行宏任务队列里面的任务;

那么基于当前的代码下,如何不适用setTimeout,模拟微任务呢?

那么在源生中,有一个方法叫postMessage,可以模拟微任务;

 _resolve () {
    /*
      将当前promise的状态由pending变为resolve状态,并执行成功后的注册函数
      如果当前状态改变过了,则return
     */
    // setTimeout(() => {
     
    // }, 0);
    window.addEventListener('message', () => {
      if (this.status !== DIYPromise.PENDING) return
      this.status = DIYPromise.RESOLVE;
      let handler;
      while (handler = this.resolvedQueues.shift()) {
        handler();
      }
    });
    window.postMessage('');
  }

_reject () {
    /*
      将当前promise的状态由pending变为reject状态,并执行失败后的注册函数
      如果当前状态改变过了,则return
     */
    // setTimeout(() => {
      
    // }, 0);

    window.addEventListener('message', () => {
      if (this.status !== DIYPromise.PENDING) return
      this.status = DIYPromise.REJECT;
      let handler;
      while (handler = this.rejectedQueues.shift()) {
        handler();
      }
    });
    window.postMessage('');
  }

 

这样就解决了上面的两个问题,但是还会一个问题,那就是当某处触发了这个message这个事件,则这个方法会被触发执行,所以可以带一下标识符window.postMessage('')做一些判断;

接下来要看一下回调传递的数据怎么处理?以及promise是如何实现.then()的链式调用的呢?

关于then的链式调用,那就涉及到promise中下面的问题:

let p1 = new Promise((res, rej) => {
      res();
    });
    // 方式一:
    p1.then(res => {
      console.log('res1')
    }).then(res => {
      console.log('res2')
    });

    // 方式二
    p1.then(res => {
      console.log('res1')
    });
    p1.then(res => {
      console.log('res2')
    });

// 方式1和方式2有区别吗?

 

方式一和方式二显然是有很大区别的:

区别可以类比下面的:下面两者的不同之处

let arr = [1,2,3,4,5,6,7,8,9,0];

    arr.slice(1,2).slice(1,2);

    arr.slice(1,2);
    arr.slice(1,2);

方式一总的两个then(),相当于都是注册给了p1对象,

方式二后面的then()相当于注册给了另外一个promise对象;这里的跟jq中的不一样,jq返回是是同一个对象

知道了then()的这种区别之后,那么DIYPromise对象如何实现这种写法呢?

所以要让then()返回一个DIYPromise对象:

then (resolvedHandler, rejectedHandler) {
    // 这里传进来的函数不能直接调用,而应该类似推入事件队列里面去
    // 理解事件原理
    // this.resolvedQueues.push(resolvedHandler);
    // this.rejectedQueues.push(rejectedHandler);
    return new DIYPromise((resolve, reject) => {
      function newResolvedHandler () {
        resolvedHandler();
        resolve();
      }
      
      function newRejectedHandler(params) {
        rejectedHandler();
        reject();
      }
      this.resolvedQueues.push(newResolvedHandler);
      this.rejectedQueues.push(newRejectedHandler);
    });
  }

在调用resolve和reject方法的时候,还可以传入一些值,在后续调用then()方法中可以通过对应的函数接收到这个值;

需要注意的是:

如果在一个promise对象的状态改变后调用then(),则会立即执行所添加的对应的函数,所以必须根据当前promise对象的状态来做不同的处理。

如果是pending状态,则添加到队列中去,如果是resolved/reject状态则是立即执行。

上面代码仍然有巨大的问题,原生promise中的then如果有return值的话,后面的then中是可以拿到这个return值,但是目前DIYPromise对象没有实现这个,所以需要将这个return值保存起来,并将结果传递给下一个resolverHandler

 then (resolvedHandler, rejectedHandler) {
    // 这里传进来的函数不能直接调用,而应该类似推入事件队列里面去
    // 理解事件原理
    // this.resolvedQueues.push(resolvedHandler);
    // this.rejectedQueues.push(rejectedHandler);
    return new DIYPromise((resolve, reject) => {
      function newResolvedHandler (params) {
        let result = resolvedHandler(params);
        resolve(result);
      }
      
      function newRejectedHandler(params) {
        let result = rejectedHandler(params);
        reject(result);
      }
      this.resolvedQueues.push(newResolvedHandler);
      this.rejectedQueues.push(newRejectedHandler);
    });
  }

但是上面的方法仅仅是return一个值的情况,如果return的是一个promise对象呢?

let p1 = new DIYPromise((resolve) => {
      resolve('终身成长');
    });
    let p2 = p1.then(res => {
      console.log('then1', res);
      return new DIYPromise((resolve) => {
        setTimeout(() => {
          console.log('settimeout');
          resolve('返回的是对象');
        }, 1000);
      });
    });
    p2.then(res => {
      console.log('then2', res);
    });

这个时候就应该做一点兼容处理:比如

then (resolvedHandler, rejectedHandler) {
    // 这里传进来的函数不能直接调用,而应该类似推入事件队列里面去
    // 理解事件原理
    // this.resolvedQueues.push(resolvedHandler);
    // this.rejectedQueues.push(rejectedHandler);
    return new DIYPromise((resolve, reject) => {
      function newResolvedHandler (params) {
        let result = resolvedHandler(params);
        if (result instanceof DIYPromise) {
          result.then(resolve, reject);
        }else {
          resolve(result);
        }
      }
      
      function newRejectedHandler(params) {
        let result = rejectedHandler(params);
        reject(result);
      }
      this.resolvedQueues.push(newResolvedHandler);
      this.rejectedQueues.push(newRejectedHandler);
    });
  }

完善一下失败的调用:



class DIYPromise {
  static PENDING = "PENDING";
  static RESOLVE = "RESOLVE";
  static REJECT = "REJECT";


  constructor ( handler ) {
    if (typeof handler !== 'function') {
      throw new TypeError('Promise resolver undefined is not a function');
    }
    this.status = DIYPromise.PENDING;
    this.resolvedQueues = [];
    this.rejectedQueues = [];
    this.value;
    handler(this._resolve.bind(this), this._reject.bind(this));
  }


  then (resolvedHandler, rejectedHandler) {
    // 这里传进来的函数不能直接调用,而应该类似推入事件队列里面去
    // 理解事件原理
    // this.resolvedQueues.push(resolvedHandler);
    // this.rejectedQueues.push(rejectedHandler);
    return new DIYPromise((resolve, reject) => {
      function newResolvedHandler (params) {
        let result = resolvedHandler(params);
        if (result instanceof DIYPromise) {
          result.then(resolve, reject);
        }else {
          resolve(result);
        }
      }
      
      function newRejectedHandler(params) {
        let result = rejectedHandler(params);
        if (result instanceof DIYPromise) {
          result.then(resolve, reject);
        }else {
          reject(result);
        }
      }
      this.resolvedQueues.push(newResolvedHandler);
      this.rejectedQueues.push(newRejectedHandler);
    });
  }
}

写个demo测试一下:

let p1 = new DIYPromise((resolve) => {
      resolve('终身成长');
    });
    let p2 = p1.then(res => {
      console.log('then1', res);
      return new DIYPromise((resolve,reject) => {
        setTimeout(() => {
          console.log('settimeout');
          reject('返回的是对象');
        }, 1000);
      });
    });
    p2.then(res => {
      console.log('then2', res);
    }, rej => {
      console.log('出问题了', rej);
    });

这个时候,控制台是有打印出对应的信息。不过在原生的promoise中,是存在用catch()方法来捕获错误信息的,catch只接受一个函数,就是错误处理函数,那么catch方法该如何实现呢?

其实很简单,catch就是调用then方法,只是resolvedHandler是undefined,那么就需要处理一下newResolvedHandler和newRejectedHandler方法做容错兼容处理

catch(rejectedHandler) {
    return this.then(undefined, rejectedHandler)
  }
function newResolvedHandler (params) {
        if (typeof resolvedHandler === 'function') {
          let result = resolvedHandler(params);
          if (result instanceof DIYPromise) {
            result.then(resolve, reject);
          }else {
            resolve(result);
          }
        }else {
          resolve(params)
        }
      }
      
      function newRejectedHandler(params) {
        if (typeof rejectedHandler === 'function') {
          let result = rejectedHandler(params);
          if (result instanceof DIYPromise) {
            result.then(resolve, reject);
          }else {
            reject(result);
          }
        }else {
          reject(params);
        }
      }

再来一个问题:promise中的finally()函数的实现又是怎么样的呢?

前面的then是正确的时候执行,catch是失败的时候执行,那么finally呢?它是正确或者失败都会执行的一个方法,所以需要添加一个数组来管理finally的函数队列:

this.finallyQueues = [];

finally最终调用的方法是_finally方法,跟resolve和reject方法类似需要接受传递过来的结构参数:

_finally(val) {
    window.addEventListener('message', () => {
      this.status = DIYPromise.RESOLVE;
      this.value = val;
      let handler;
      while (handler = this.finallyQueues.shift()) {
        handler(this.value);
      }
    });
    window.postMessage('');
  }

因为不管是成功还是失败都会调用finally方法,所以需要在resolve和reject方法总调用_finally()方法,并将对应的结果参数传递进函数,至此finally方法就可以正常使用了:

 new DIYPromise((resolve, reject) => {
      // resolve('终身成长');
      reject('错误');
    }).then(res => {
      console.log('then2', res);
      return '124' -->如果没有return值,那么finally接受到的参数将会是undefined
    }).finally((val) => {
      console.log('finally', val);
    });

接下来将实现静态方法resolve,在原生的promise中,可以直接调用resolve方法,其实使用的就是静态方法resolve

static resolve(val) {
    return new DIYPromise((resolve) => {
      resolve(val);
    });
  }

  static reject(val) {
    return new DIYPromise((resolve, reject) => {
      reject(val);
    });
  }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值