一文带你手写最全的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 的状态执行

结束语

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

相关推荐
程序员凡尘27 分钟前
完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法,亲测有效!!!
前端·javascript·vue.js
编程零零七4 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
北岛寒沫5 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
everyStudy5 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
(⊙o⊙)~哦6 小时前
JavaScript substring() 方法
前端
无心使然云中漫步6 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者6 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_7 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋8 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120538 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab