> 文档中心 > 手写Promise--第一篇

手写Promise--第一篇


前言

众所周知由于多层回调函数相互嵌套,代码耦合性太强,牵一发而动全身,导致代码难以维护,可读性差,所以为了解决回调地狱问题,ES6新增了Promise的概念。相信内置的Promise大家都会使用了,但是Promise实现的原理,大家一定还很陌生,本篇文章将带领大家一步步封装自己的Promise;

手写Promise--第一篇

文章の目录

  • 前言
  • 手写Promise前必读
  • 🥇 实现Promise的基本功能
  • 🥇 then()方法的实现
  • 写在最后

手写Promise前必读

在手写Promise之前我们需要了解Promise的三个状态,Promise是一个有状态的对象,可能处于如下 3 种状态之一:
待定(pending)
兑现(fulfilled,有时候也称为“解决”,resolved)
拒绝(rejected)
待定(pending)是Promise的最初始状态。在待定状态下,Promise可以落定(settled)为代表成功的兑现(fulfilled)状态,或者代表失败的拒绝(rejected)状态。无论落定为哪种状态都是不可逆的。只要从待定转换为兑现或拒绝,Promise的状态就不再改变。而且,也不能保证期约必然会脱离待定状态。因此,组织合理的代码无论Promise解决(resolve)还是拒绝(reject),甚至永远处于待定(pending)状态,都应该具有恰当的行为。重要的是,Promise的状态是私有的,不能直接通过 JavaScript 检测到。这主要是为了避免根据读取到的期约状态,以同步方式处理Promise对象。另外,Promise的状态也不能被外部 JavaScript 代码修改。这与不能读取该状态的原因是一样的:Promise故意将异步行为封装起来,从而隔离外部的同步代码。
📣注意:在Promise的状态发生改变后就会发生状态凝固,即状态不能再次改变;

🥇 实现Promise的基本功能

首先我们先用内置Promises创建一个实例对象,传入一个函数,内部打印11,运行后发现控制台输出了11,说明传入Promise的函数在new一个Promise实例时,会立即调用;
在这里插入图片描述
那么我们如何实现此功能呢?
实际上在创建Promise实例对象时传入的参数叫做执行器函数(executor),若想new一个Promise的同时调用函数,执行器的函数的调用必须写在Promise类的constructor函数内部,这样每次创建Promise实例时,都会执行constructor函数,即可调用executor函数;在调用执行器函数的同时我们需要传入resolve、reject函数用来处理Promise的状态,状态即为前面提到的三种状态;
代码示例如下所示:

class Promise {  constructor(executor) {    //调用执行器函数    executor(resolve, reject)  }}

我们将此js代码引入一个html页面覆盖内置的Promise测试此功能;如果能正常打印11即为成功;
在这里插入图片描述
实现以上功能以后我们接着定义构造器函数(executor)内部的两个处理Promise状态的函数resolve 和 reject,在定义两个状态处理函数前我们要先初始化Promise的初始状态为pending(待定),而且要先定义resolve 和 reject函数执行成功的结果和执行失败的原因;
代码示例如下:

class Promise {  constructor(executor) {    this.state = 'pending'//Promise初始状态    this.value = undefined//执行成功的结果    this.reason = undefine//执行失败的结果    //调用执行器函数    executor(resolve, reject)  }}

在定义Promise的两个改变状态的函数前我们需要了解Promise/A+规范的关于状态凝固的概念,即resolve和reject函数处理Promise状态是不可逆的,即执行完函数改变Promise状态后不能再次改变Promise的状态即状态凝固了;
基于规范的状态改变的不可逆性,我们需要在函数内部加上判断条件,即当Promise的状态为pending时才能进入代码块执行代码;代码示例如下:

class Promise {  constructor(executor) {    this.state = 'pending' //Promise初始状态    this.value = undefined //执行成功的结果    this.reason = undefine //执行失败的结果    // 定义resolve函数    const resolve = (value) => {      if (this.state === 'pending') { this.state = 'fulfiled' this.value = value      }    }    //定义reject函数    const reject = (reason) => {      if (this.state === 'pending') { this.state = 'rejected' this.reason = reason      }      //调用执行器函数      executor(resolve, reject)    }  }}

实现以上功能后,我们需要对代码进行优化,即执行器函数调用时,可能发生一些未知的错误,为了避免程序因为抛出错误导致程序崩溃,我们将执行器函数调用放到try-catch语句中;
优化后代码如下:

//捕获Promise状态发生错误的第三种方式     try {      //调用执行器函数 executor(resolve, reject)     } catch (err) {      reject(err)     }

🥇 then()方法的实现

在Promise状态发生改变后,无论成功后者失败,都会触发then内部的回调函数;然后根据Promise改变后的状态,去判断是执行成功的回调函数还是失败的回调函数;代码示例如下:

class Promise {  constructor(executor) {    this.state = 'pending' //Promise初始状态    this.value = undefined //执行成功的结果    this.reason = undefine //执行失败的结果    // 定义resolve函数    const resolve = (value) => {      if (this.state === 'pending') { this.state = 'fulfiled' this.value = value      }    }    //定义reject函数    const reject = (reason) => {      if (this.state === 'pending') { this.state = 'rejected' this.reason = reason      }      //调用执行器函数      executor(resolve, reject)    }  }  then(onResolved, onRejected) {    //Promise状态为resolved时    if (this.state === 'resolved') {      onResolved(this.value)    }    //Promise状态为rejected时    if (this.state === 'rejected') {      onRejected(this.reason)    }  }}

定义完then()方法后进行测试,发现在执行resolve或者执行reject函数改变Promise状态时,如果函数的调用是异步的,就会导致在调用then方法时,异步的代码还未执行,Promise状态还是pending,就无法执行then方法内部的成功和失败的回调函数;
测试失败的demo如下:

var myP = new Promise(function(resolve, reject){    console.log('执行')    setTimeout(function(){ reject(3)    }, 1000)});myP.then(function(res){    console.log(res)},function(err){    console.log(err)});

因为以上reject(3) ,在定时器里面是异步的,而then()方法是同步的,所以控制台并未打印出3;执行流程如下图所示;
在这里插入图片描述
解决此问题思路,在then方法内若判断Promise状态是pending,说明改变Promise状态的函数是异步的,还未执行,此时我们直接将失败和成功后调用的回调函数存到一个数组中,当执行完异步队列中的改变Promise状态的函数时再进行调用;优化后的then()

then(onResolved, onRejected) {    //Promise状态为resolved时    if (this.state === 'resolved') {      onResolved(this.value)    }    //Promise状态为rejected时    if (this.state === 'rejected') {      onRejected(this.reason)    }    // 如果状态为pending,将回调函数存如数组    if (this.state === 'pending') {      //将执行成功的回调函数存入数组      this.resolvedCallback.push(() => { onResolved(this.value)      })    }    //将执行失败的回调函数存入数组    this.rejectedCallback.push(() => {      onRejected(this.reason)    })  }

以上的resolvedCallback、rejectedCallback为定义的两个空数组;将回调函数存入数组后,在执行异步队列中的函数时,遍历数组,然后再执行数组中存的成功或失败的回调函数;
优化resolve和reject函数后的完整代码如下所示;

class Promise {  constructor(executor) {    this.state = 'pending' //Promise初始状态    this.value = undefined //执行成功的结果    this.reason = undefine //执行失败的结果    //定义数组用来存放回调函数    this.resolvedCallback = []    this.rejectedCallback = []    // 定义resolve函数    const resolve = (value) => {      if (this.state === 'pending') { this.state = 'fulfiled' this.value = value //  改变完状态执行数组中的回调函数 this.resolvedCallback.forEach((item) => item())      }    }    //定义reject函数    const reject = (reason) => {      if (this.state === 'pending') { this.state = 'rejected' this.reason = reason // 改变完状态执行数组中的回调函数 this.rejectedCallback.forEach((item) => item())      }      //调用执行器函数      executor(resolve, reject)    }  }  then(onResolved, onRejected) {    //Promise状态为resolved时    if (this.state === 'resolved') {      onResolved(this.value)    }    //Promise状态为rejected时    if (this.state === 'rejected') {      onRejected(this.reason)    }    // 如果状态为pending,将回调函数存如数组    if (this.state === 'pending') {      //将执行成功的回调函数存入数组      this.resolvedCallback.push(() => { onResolved(this.value)      })    }    //将执行失败的回调函数存入数组    this.rejectedCallback.push(() => {      onRejected(this.reason)    })  }}

此时就完成了then()方法的包括支持异步的功能;本片文章就讲到这里,下篇文章将接着带领大家实现then方法的链式调用功能;

写在最后

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞