手写Promise--第一篇
前言
众所周知由于多层回调函数相互嵌套,代码耦合性太强,牵一发而动全身,导致代码难以维护,可读性差,所以为了解决回调地狱问题,ES6新增了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方法的链式调用功能;
写在最后
🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞