IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 手撕promise--promise的底层实现<究极详细> -> 正文阅读

[JavaScript知识库]手撕promise--promise的底层实现<究极详细>

手撕promise

promise有三种状态

请添加图片描述

创建初始结构

// 初始结构
class Commitment {
  static PENDING='待定';static FULFILLED='成功';static REJECT='拒绝';
  constructor(func){
    // status状态
    this.status = Commitment.PENDING
    // result对应状态的结果
    this.result = null
    func(this.resolve,this.reject)
  }
  // 因为resolve和reject也是方法
  resolve(result){
    if(this.status === Commitment.PENDING){
      this.status = Commitment.FULFILLED
      this.result = result
    }
  }
  reject(result){
    if(this.status === Commitment.PENDING){
      this.status = Commitment.REJECT
      this.result = result
    }
  }
}

let commitment = new Commitment(resolve=>{
  resolve('123')
})

写到这里直接new一个Commitment实例,结果会报错

请添加图片描述

为什么找不到status呢,因为constructor里面的this指向的是promise实例,而constructor外面指向的是其构造函数的原型对象

因此要修改this指向

修改this指向

class Commitment {
  static PENDING='待定';static FULFILLED='成功';static REJECT='拒绝';
  constructor(func){
    // status状态
    this.status = Commitment.PENDING
    // result对应状态的结果
    this.result = null
      // 在这里为resolve和reject函数修改this指向
    func(this.resolve.bind(this),this.reject.bind(this))
  }

接下来实现.then

class Commitment {
  then(onFULFILLED,onREJECT){
    if(this.status === Commitment.FULFILLED){
      onFULFILLED(this.result)
    }
    if(this.status === Commitment.REJECT){
      onREJECT(this.result)
    }
  }
}

解决异常

异常一
// 原生promise
let promise = new Promise((resolve,reject)=>{
    throw new Error('promise错误')
})
promise.then(
  result=>{console.log(result)},
  result=>{console.log(result.message)}// promise错误
)

在原生promise中,如果promise自己报错,then的第二个函数能接收到,因此我们要用try…catch捕获

class Commitment {
  static PENDING='待定';static FULFILLED='成功';static REJECT='拒绝';
  constructor(func){
    // status状态
    this.status = Commitment.PENDING
    // result对应状态的结果
    this.result = null
    try{
      func(this.resolve.bind(this),this.reject.bind(this))
    } catch(error){
      this.reject(error)
    }
  }
}
异常二
// 原生promise
let promise = new Promise((resolve,reject)=>{
    resolve('成功')
    reject('失败')
})
promise.then(
  undefined,
  result=>{console.log(result.message)}
)
// 不会报错

在原生promise中,.then里的两个参数如果不为函数,也不会报错;但是我们的手写代码会报错,因此要加异步判断

class Commitment {
  then(onFULFILLED,onREJECT){
    // 如果参数是一个函数,就把自己赋值给自己(不变),如果不是函数,则赋一个空函数
    onFULFILLED = typeof onFULFILLED === 'function' ? onFULFILLED : ()=>{}
    onREJECT = typeof onREJECT === 'function' ? onREJECT : ()=>{}
    if(this.status === Commitment.FULFILLED){
      onFULFILLED(this.result)
    }
    if(this.status === Commitment.REJECT){
      onREJECT(this.result)
    }
  }
}

实现异步

要实现异步,先套上setTimeout

  then(onFULFILLED,onREJECT){
    // 如果参数是一个函数,就把自己赋值给自己(不变),如果不是函数,则赋一个空函数
    onFULFILLED = typeof onFULFILLED === 'function' ? onFULFILLED : ()=>{}
    onREJECT = typeof onREJECT === 'function' ? onREJECT : ()=>{}
    if(this.status === Commitment.FULFILLED){
      setTimeout(()=>{
        onFULFILLED(this.result)
      })
    }
    if(this.status === Commitment.REJECT){
      setTimeout(()=>{
        onREJECT(this.result)
      })
    }
  }

之后会有一个问题

在说这个问题前,要先了解什么是宏任务和微任务

宏任务包括:

  • 异步ajax请求
  • setTimeout、setInterval
  • 文件操作
  • 其他

微任务包括:

  • Promise.then .catch .finally(注意是Promise的这三个方法,Promise实例本身还是属于同步任务的)
  • process.nextTick
  • 其他

代码的执行流程为:同步任务–>微任务–>下一个宏任务–>下一个宏任务里的微任务

// 原生promise
console.log('1')

let promise = new Promise((resolve,reject)=>{
	console.log('2')
	setTimeout(()=>{
		resolve('5')
		console.log('4')
	})
})
promise.then(
	result =>{console.log(result)}
)
console.log('3')

// 输出结果为12345

但是按照刚刚讲的,同步任务执行完输出123之后,应该先执行微任务.then,此时resolve(‘5’)还为执行,所以应该没有输出,之后再执行宏任务setTimeout输出4

结果应为1234

说明了,原生promise对于这里进行了处理

实现方法:

因为执行then之前没有执行resolve,导致then当时的状态为PENDING待定状态,所以我们加一步判断,判断当前状态为待定时,让then稍后再执行,执行完了resolve再执行then

具体的实现为,创建this.resolveCallbacks=[]保存resolve的执行结果
创建this.rejectCallbacks=[]保存reject的执行结果
在then里,判断当前状态为待定时,将对应的函数onFULFILLED,onREJECT都push到对应数组中
之后在resolve和reject里遍历数组

这里还有一个坑,resolve和reject在事件循环末尾执行,所以要在resolve和reject里加上setTimeout

实现代码

class Commitment {
  static PENDING='待定';static FULFILLED='成功';static REJECT='拒绝';
  constructor(func){
    // status状态
    this.status = Commitment.PENDING
    // result对应状态的结果
    this.result = null
    // 保存resolve函数
    this.resolveCallbacks=[]
    // 保存reject函数
    this.rejectCallbacks=[]
    try{
      func(this.resolve.bind(this),this.reject.bind(this))
    } catch(error){
      this.reject(error)
    }
  }
  // 因为resolve和reject也是方法
  resolve(result){
    setTimeout(()=>{
      if(this.status === Commitment.PENDING){
        this.status = Commitment.FULFILLED
        this.result = result
        this.resolveCallbacks.forEach(
          callback=>{
            callback(result)
          }
        )
      }
    })
  }
  reject(result){
    setTimeout(()=>{
      if(this.status === Commitment.PENDING){
        this.status = Commitment.REJECT
        this.result = result
        this.rejectCallbacks.forEach(
          callback=>{
            callback(result)
          }
        )
      }
    })
  }
  then(onFULFILLED,onREJECT){
    // 如果参数是一个函数,就把自己赋值给自己(不变),如果不是函数,则赋一个空函数
    onFULFILLED = typeof onFULFILLED === 'function' ? onFULFILLED : ()=>{}
    onREJECT = typeof onREJECT === 'function' ? onREJECT : ()=>{}
    // 这一步判断PENDING状态是为了实现异步
    if(this.status === Commitment.PENDING ){
      this.resolveCallbacks.push(onFULFILLED)
      this.resolveCallbacks.push(onREJECT)
    }
    if(this.status === Commitment.FULFILLED){
      setTimeout(()=>{
        onFULFILLED(this.result)
      })
    }
    if(this.status === Commitment.REJECT){
      setTimeout(()=>{
        onREJECT(this.result)
      })
    }
  }
}

带大家理一下顺序

let promise = new Promise((resolve,reject)=>{
	console.log('2')
	setTimeout(()=>{
		resolve('5')
		console.log('4')
	})
})
promise.then(
	result =>{console.log(result)}
)

/*
**new一个实例输出2,运行遇到setTimeout异步先不执行,到then方法
**then里面发现现在的状态是待定,就把函数参数push到对应数组
**现在开始执行setTimeout里面的内容,先执行resolve
**发现resolve函数上来就是一个setTimeout异步,只能出来先执行console.log('4'),输出4
**最后执行在消息队列中的resolve函数
*/

实现链式

把then中的功能返回一个新的promise就可以了

  then(onFULFILLED,onREJECT){
    return new Commitment((resolve,reject)=>{
      // 如果参数是一个函数,就把自己赋值给自己(不变),如果不是函数,则赋一个空函数
      onFULFILLED = typeof onFULFILLED === 'function' ? onFULFILLED : ()=>{}
      onREJECT = typeof onREJECT === 'function' ? onREJECT : ()=>{}
      // 这一步判断PENDING状态是为了实现异步
      if(this.status === Commitment.PENDING ){
        this.resolveCallbacks.push(onFULFILLED)
        this.resolveCallbacks.push(onREJECT)
      }
      if(this.status === Commitment.FULFILLED){
        setTimeout(()=>{
          onFULFILLED(this.result)
        })
      }
      if(this.status === Commitment.REJECT){
        setTimeout(()=>{
          onREJECT(this.result)
        })
      }
    })
  }

最终代码

class Commitment {
  static PENDING='待定';static FULFILLED='成功';static REJECT='拒绝';
  constructor(func){
    // status状态
    this.status = Commitment.PENDING
    // result对应状态的结果
    this.result = null
    // 保存resolve函数
    this.resolveCallbacks=[]
    // 保存reject函数
    this.rejectCallbacks=[]
    try{
      func(this.resolve.bind(this),this.reject.bind(this))
    } catch(error){
      this.reject(error)
    }
  }
  // 因为resolve和reject也是方法
  resolve(result){
    setTimeout(()=>{
      if(this.status === Commitment.PENDING){
        this.status = Commitment.FULFILLED
        this.result = result
        this.resolveCallbacks.forEach(
          callback=>{
            callback(result)
          }
        )
      }
    })
  }
  reject(result){
    setTimeout(()=>{
      if(this.status === Commitment.PENDING){
        this.status = Commitment.REJECT
        this.result = result
        this.rejectCallbacks.forEach(
          callback=>{
            callback(result)
          }
        )
      }
    })
  }
  then(onFULFILLED,onREJECT){
    return new Commitment((resolve,reject)=>{
      // 如果参数是一个函数,就把自己赋值给自己(不变),如果不是函数,则赋一个空函数
      onFULFILLED = typeof onFULFILLED === 'function' ? onFULFILLED : ()=>{}
      onREJECT = typeof onREJECT === 'function' ? onREJECT : ()=>{}
      // 这一步判断PENDING状态是为了实现异步
      if(this.status === Commitment.PENDING ){
        this.resolveCallbacks.push(onFULFILLED)
        this.resolveCallbacks.push(onREJECT)
      }
      if(this.status === Commitment.FULFILLED){
        setTimeout(()=>{
          onFULFILLED(this.result)
        })
      }
      if(this.status === Commitment.REJECT){
        setTimeout(()=>{
          onREJECT(this.result)
        })
      }
    })
  }
}
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-09 12:31:32  更:2022-05-09 12:32:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/11 5:36:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码