一文带你手写最全的Promise【简易版本】

前言

早上打开掘金无意间发现之前写的一篇文章还没发布,忘记当时是什么原因搁置了。翻看内容有些已经忘记了,正好巩固并完善下,就当作自己的学习记录,下面是手写实现promise以及它的一些方法,加深对其的理解。

以下主要是手写Promise相关方法的简易版本,详细知识可去Promise 对象 - ECMAScript 6入门 (ruanyifeng.com)查看。

一、Promise

  • Promise对象代表一个异步操作,有三种状态:pending(进行中)、fullfilled(已成功)和rejected(已失败)
  • Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果
ini 复制代码
//定义表示promise对象的3种状态的常量
const PENDING='pending'
const FULFILLED='fullfilled'
const REJECTED='rejected'

function MyPromise(fn){
  let _this = this //防止被修改,_this指向调用它的对象
  _this.state=PENDING //初始时状态为PENDING(进行中)
  _this.value=null  //初始时值为空
  _this.resolvedCallback=[] //用来装等待执行的fulfilled回调函数
  _this.rejectedCallback=[] //用来装等待执行的rejected回调函数

  function resolve(value){
    if(_this.state===PENDING){//说明状态从pending -> fulfilled
      _this.state=FULFILLED //修改状态和值
      _this.value=value
      _this.resolvedCallback.map(cb=>{ //执行掉resolvedCallback中全部的回调函数
        cb(_this.value)
      })
    }
  }

  function reject(value){
    if(_this.state===PENDING){//说明状态从pending -> rejected
      _this.state=REJECTED //修改状态和值
      _this.value=value
      _this.rejectedCallback.map(cb=>{ //执行掉rejectedCallback中全部的回调函数
        cb(_this.value)
      })
    }
  }

  try {
    fn(resolve,reject)
  } catch (error) {
    reject(error)
  }

}

这样基本上实现了简单的Promise~

1. then方法

  • then方法可以接受两个回调函数作为参数。第一个是Promise对象的状态变为resolved时调用,第二个是Promise对象的状态变为rejected时调用。它们都是可选的。
  • then方法返回的是一个新的Promise实例
  • 为了实现then方法的异步,这里借用了setTimeout实现(不是最优),让里面的函数在状态还未改变时挂起
scss 复制代码
MyPromise.prototype.then=function(onFulfilled,onRejected) {//then方法接收2个参数
  let _this=this //指向调用它的对象
  onFulfilled=typeof onFulfilled==='function' ? onFulfilled:c=>c //判断传的参数是否为函数,是的话就不用操作,
  //不是的话手动将其改成函数的形式
  onRejected=typeof onRejected==='function'?onRejected:r=>{throw r}

    return new MyPromise((resolve,reject)=>{//返回Promise实例
    if(_this.state===PENDING){//表明此时pending状态,将参数回调函数加到数组中,为了方便下次状态更改的时候可以执行掉之前被阻塞的
      _this.resolvedCallback.push(value=>{
        setTimeout(()=>{ //用定时器主要是为了模拟then里的方法是异步的
          const res=onFulfilled(value)
          resolve(res)
        })
      })
      _this.rejectedCallback.push(reason=>{
        setTimeout(()=>{
          const res=onRejected(reason)
          reject(res)
        })
      })
    }
  
    if(_this.state===FULFILLED){//状态更改为fulfilled 
      setTimeout(()=>{ //用定时器主要是为了模拟then里的方法是异步的
        try {
          const result=onFulfilled(_this.value)
          resolve(result)
        } catch (error) {
          reject(error)
        }
      })
    }
  
    if(_this.state===REJECTED){
      setTimeout(()=>{
        try {
          const result=onRejected(_this.value)
          reject(result)
        } catch (error) {
          reject(error)
        }
      })
    }
  })
}

2. catch方法

  • Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,捕获Promise的异常错误
  • catch()方法返回的还是一个 Promise 对象
javascript 复制代码
MyPromise.prototype.catch=function(onRejected){
  let _this = thi
  return _this.then(null,onRejected)
}
javascript 复制代码
//测试
let p1=new MyPromise((resolve, reject)=>{
  resolve('ok')
})
p1.then(
  (res)=>{
  console.log(res);
  }
).catch(err=>{
  console.log(err);
}) //ok

let p2=new MyPromise((resolve, reject)=>{
  reject('error')
})
p2.then(
  (res)=>{
  console.log(res);
  }
).catch(err=>{
  console.log(err);
}) //error

3. finally方法

  • 不管 Promise 对象最后状态如何,都会执行的操作。也就是finally与状态无关的,不依赖于 Promise 的执行结果
  • 返回promise 对象
  • finally会把前面resolve的值传给后面用
javascript 复制代码
MyPromise.prototype.MyFinally=function(callback){
  let P=this.constructor //确保能够在Promise链中调用正确的resolve和reject方法
  return this.then( //this代表指向调用它的对象
    value => P.MyResolve(callback()).then(()=>value),
    reason => P.MyReject(callback()).then(()=>{throw reason})
  )
}

//测试
MyPromise.MyResolve(2)
  .then(()=>console.log('resolve'))
  .MyFinally(()=>console.log('finally'))
  //resolve finally

MyPromise.MyReject(3).MyFinally(()=>console.log('finally')) //finally

let p=new MyPromise((resolve, reject)=>{
  resolve('ok')
})
p.then(
  (res)=>{
  setTimeout(()=>{
    console.log(res);
  })
  return res
  }
)
.MyFinally(val=>{
  console.log(123);
})
.then((res2)=>{
  console.log(res2);
})
//ok
//123
//MyPromise {
//  state: 'fullfilled',
//  value: 'ok',
//  resolvedCallback: [],
//  rejectedCallback: []
//}

引入this.constructor存放在变量P的原因:通常情况下,通过this.constructor可以获取到当前Promise实例的构造函数,即Promise类本身。但有时候可能this.constructor被修改了,不再是原来的Promise类本身,所以才引入这个。

4. Promise.resolve()

  • 将现有对象转为 Promise 对象
  • 它的参数分多种情况,这里不细说了
javascript 复制代码
//比较简单,不做过多说明
MyPromise.MyResolve=function(val){
  return new MyPromise((resolve, reject) =>{
    resolve(val)
  })
}

//测试
MyPromise.MyResolve('hello')
  .then(res => console.log(res))//hello

5. Promise.reject()

  • 返回一个新的Promise实例,该实例的状态为rejected
  • 参数作为reject的理由
javascript 复制代码
//比较简单,不做过多说明
MyPromise.MyReject=function(val){
  return new MyPromise((resolve, reject) =>{
    reject(val)
  })
}

//测试
MyPromise.MyReject('error')
  .then(null,res => console.log(res)) //error

6. Promise.all()

  • 返回一个promise实例
  • 参数接收一个有iterable类型的容器,容器中每个元素都是promise实例,假设这个容器就是数组
  • 数组中每个promise实例都为fulfilled状态时,Promise.all()fulfilled状态,并且resolve的结果为数组中promise实例的返回的resolve结果,也以数组形式承接
  • 如果有一个promise实例的状态rejected,则返回第一个promisereject的reason
javascript 复制代码
MyPromise.MyAll=function(promises){
  return new MyPromise((resolve, reject) =>{ //Promise.all的结果返回一个promise实例
  let arr=[],count=0 //用数组来装结果,count计数
    promises.forEach((item,i)=>{
      item.then(
      (res)=>{ //状态fulfilled的添加进数组
        arr[i]=res 
        count++
        if(count===promises.length){//如果promises的参数全都fulfilled,则all方法也fulfilled
          resolve(arr)
        }
       },
       (reason)=>{ //只有有一个状态rejected的则直接返回此all方法状态也rejected
        reject(reason)
        return 
       }
      )
    })
  })
}


//测试 此测试后面都会用到,就不重复写了
const p1 = MyPromise.MyResolve('p1')
const p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2 延时一秒')
  }, 1000)
})
const p3 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('p3 延时两秒')
  }, 2000)
})
const p4 = MyPromise.MyReject('p4 rejected')

const p5 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
     reject('p5 rejected 延时0.5秒')
    }, 500)
 })
 
 MyPromise.MyAll([p1, p2, p3])
  .then(res => console.log(res)) //[ 'p1', 'p2 延时一秒', 'p3 延时两秒' ]
  .catch(err => console.log(err))

由于后面的代码写法差不多,所以后面几个代码就不写备注了

7. Promise.race()

  • 将多个 Promise 实例,包装成一个新的 Promise 实例
  • 返回最先执行结束的promise的返回值,无论状态是rejected还是fulfilled
javascript 复制代码
MyPromise.MyRace=function(promises){
  return new MyPromise((resolve, reject) =>{
    for(let item of promises){
      item.then( //返回 最先改变状态的
        (value)=>{ 
          resolve(value)
        },
        (reason)=>{
          reject(reason)
        }
      )
    }
  })
}

//测试 继续上面的测试
MyPromise.MyRace([p2,p3,p5])
  .then(res => console.log(res))
  .catch(err => console.log(err))//'p5 rejected 延时0.5秒'
  

8. Promise.allSettled()

  • 接受一个数组 作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象
  • 只有等到参数数组的所有 Promise 对象都发生状态变更,不管是fulfilled还是rejected,返回的promise对象都会发生状态改变
  • 返回的对象状态变更后状态总是fulfilled,不会变成rejected
javascript 复制代码
MyPromise.MyAllSettled=function(promises){
  let arr=[]
  return new MyPromise((resolve, reject) =>{//返回promise实例
    let arr=[],count=0
    promises.forEach((item,index)=>{
      item.then(
        value=>{
        arr[index]={'status':'fulfilled','value':value}
        count++
        if(count===promises.length){
          resolve(arr)
        }
      },reason=>{
        arr[index]={'status':'rejected','value':reason}
        count++
        if(count===promises.length){
          resolve(arr)
        }
      })
    })
  })
}

//测试
MyPromise.MyAllSettled([p2,p3,p5])
.then(res => console.log(res))
.catch(err => console.log(err))

// [
//  { status: 'fullfied', value: 'p2 延时一秒' },
//  { status: 'fullfied', value: 'p3 延时两秒' },
//  { status: 'rejected', value: 'p5 rejected 延时0.5秒' }
// ]

9. Promise.any()

  • 接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回
  • 只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态
javascript 复制代码
MyPromise.MyAny=function(promises){
  return new MyPromise((resolve,reject) =>{
  let errors=[],count=0
    promises.forEach((item,index)=>{
      item.then(
      value=>{ //只有有一个状态fulfilled则resolve
        resolve(value)
        return
      },
      reason=>{
        errors[index]=reason
        count++
        if(count===promises.length){
          reject(new AggregateError(errors,'All promises were rejected'))
        }
      }
      )
    })
  })
}

//测试
const p6 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject('p5 rejected 延时1.5秒')
  }, 1500)
})

MyPromise.MyAny([p3,p5])
.then(res => console.log(res)) //'p3 延时两秒'
.catch(err => console.log(err))

MyPromise.MyAny([p5,p6])
.then(res => console.log(res))
.catch(err => console.log(err))

//AggregateError: All promises were rejected
//[errors]: [ 'p5 rejected 延时0.5秒', 'p5 rejected 延时1.5秒' ]

最终版

javascript 复制代码
const PENDING='pending'
const FULFILLED='fullfilled'
const REJECTED='rejected'

function MyPromise(fn){
  let _this = this
  _this.state=PENDING
  _this.value=null
  _this.resolvedCallback=[]
  _this.rejectedCallback=[]

  function resolve(value){
    if(_this.state===PENDING){
      _this.state=FULFILLED
      _this.value=value
      _this.resolvedCallback.map(cb=>{
        cb(_this.value)
      })
    }
  }

  function reject(value){
    if(_this.state===PENDING){
      _this.state=REJECTED
      _this.value=value
      _this.rejectedCallback.map(cb=>{
        cb(_this.value)
      })
    }
  }

  try {
    fn(resolve,reject)
  } catch (error) {
    reject(error)
  }

}

MyPromise.prototype.then=function(onFulfilled,onRejected) {
  let _this=this
  onFulfilled=typeof onFulfilled==='function' ? onFulfilled:c=>c
  onRejected=typeof onRejected==='function'?onRejected:r=>{throw r}

  return new MyPromise((resolve,reject)=>{
    if(_this.state===PENDING){
      _this.resolvedCallback.push(value=>{
        setTimeout(()=>{
          const res=onFulfilled(value)
          resolve(res)
        })
      })
      _this.rejectedCallback.push(reason=>{
        setTimeout(()=>{
          const res=onRejected(reason)
          reject(res)
        })
      })
    }
  
    if(_this.state===FULFILLED){
      setTimeout(()=>{
        try {
          const result=onFulfilled(_this.value)
          resolve(result)
        } catch (error) {
          reject(error)
        }
      })
    }
  
    if(_this.state===REJECTED){
      setTimeout(()=>{
        try {
          const result=onRejected(_this.value)
          reject(result)
        } catch (error) {
          reject(error)
        }
      })
    }
  }) 
}
MyPromise.prototype.catch=function(onRejected){
  let _this = this
  return _this.then(null,onRejected)
}

MyPromise.MyResolve=function(val){
  return new MyPromise((resolve, reject) =>{
    resolve(val)
  })
}

MyPromise.MyReject=function(val){
  return new MyPromise((resolve, reject) =>{
    reject(val)
  })
}

MyPromise.prototype.MyFinally=function(callback){
  let P=this.constructor //确保能够在Promise链中调用正确的resolve和reject方法
   return this.then(
    value => P.MyResolve(callback()).then(()=>value),
    reason => P.MyReject(callback()).then(()=>{throw reason})
  )
}
MyPromise.MyAll=function(promises){
  return new MyPromise((resolve, reject) =>{
  let arr=[],count=0
    promises.forEach((item,i)=>{
      // console.log(item);
      item.then((res)=>{
        arr[i]=res
        count++
        if(count===promises.length){
          resolve(arr)
        }
       },
       (reason)=>{
        reject(reason)
        return
       }
      )
    })
  })
}
MyPromise.MyRace=function(promises){
  return new MyPromise((resolve, reject) =>{
    for(let item of promises){
      item.then(
        (value)=>{
          resolve(value)
        },
        (reason)=>{
          reject(reason)
        }
      )
    }
  })
}
MyPromise.MyAllSettled=function(promises){
  return new MyPromise((resolve, reject) =>{
  let arr=[],count=0
    promises.forEach((item,index)=>{
      item.then(
        value=>{
        arr[index]={'status':'fulfilled','value':value}
        count++
        if(count===promises.length){
          resolve(arr)
        }
      },reason=>{
        arr[index]={'status':'rejected','value':reason}
        count++
        if(count===promises.length){
          resolve(arr)
        }
      })
    })
  })
}
MyPromise.MyAny=function(promises){
  return new MyPromise((resolve,reject) =>{
  let errors=[],count=0
    promises.forEach((item,index)=>{
      item.then(
      value=>{
        resolve(value)
        return
      },
      reason=>{
        errors[index]=reason
        count++
        if(count===promises.length){
          reject(new AggregateError(errors,'All promises were rejected'))
        }
      }
      )
    })
  })
}

二、应用

  • 异步加载图片(用promise,或者定时器加载实现,也就是利用 宏任务 或者 微任务原理)
  • 红绿灯算法

加载图片的应用这里就不细讲了,阮一峰老师的 《ECMAScript 6 入门教程》 有写,下面我们来看下红绿灯算法问题。

实现:红灯3秒亮一次,绿灯1秒亮一次,黄灯2秒亮一次,如何让3个灯不断交替重复亮

答案:

scss 复制代码
function red() {
  console.log('red');
}
function green() {
  console.log('green');
}
function yellow() {
  console.log('yellow');
}

//封装一个方法
function light(cb,time){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      cb()
      resolve()
    },time)
  })
}

const step=function(){
  Promise.resolve()
  .then(()=>{
    return light(red,3000)
  })
  .then(()=>{
    return light(green,1000)
  })
  .then(()=>{
    return light(yellow,2000)
  })
  .then(()=>{
    step()
  })
} 

//为了实现题目要求的效果,利用Promise实现,并且在 3s后红灯亮,此时再等1s后绿灯亮,绿灯亮后
//再等2s黄灯亮,如此反复。那么就在每个then 后return 值并且这个值是promise对象
//那么then里的操作会等前面要返回的promise值fulfilled再执行
step() // red green yellow 交替执行打印

注:

  • 当第1个then没有人为返回一个Promise对象时,那就默认返回resolved状态。那么第2个then也会默认执行
  • 当第1个then有人为返回一个Promise对象时,那么第2个then的执行就得依据第一个then 的状态执行

结束语

本篇文章就到此为止啦,由于本人经验水平有限,难免会有纰漏,对此欢迎指正 。如觉得本文对你有帮助的话,欢迎点赞收藏❤❤❤。要是您觉得有更好的方法,欢迎评论,提出建议!

相关推荐
咖啡の猫36 分钟前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲3 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5814 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路4 小时前
GeoTools 读取影像元数据
前端
ssshooter4 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友4 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry5 小时前
Jetpack Compose 中的状态
前端
dae bal6 小时前
关于RSA和AES加密
前端·vue.js
柳杉6 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog6 小时前
低端设备加载webp ANR
前端·算法