重写Promise源码的渐进式总结,以及对JS异步的感悟

闭包、原型及异步被号称是JS基础知识的三座大山,其中,异步对于初学者来说算是一坐巍峨雄峰了,异步这个概念在计算机知识中本身不算复杂,但由于JS语言的独特性,导致JS中的异步理解及操作稍显费劲。
本文原本是记录Promise源码的渐进式实现过程的手记,后来我补充了很多自己关于JS异步的理解,最终形成了一篇内容还算饱满的文章,描述可能并不准确,仅作参考。


在这里插入图片描述

一、我的异步探索史

1. JS为何需要异步

曾几何时,我们都习惯于按“从上到下”的方式去理解代码,上一行代码没有执行完毕,下一行代码便会一直等着它,这种代码执行的方式便是 同步
但是,我们在编程的过程中经常需要执行一些耗时的任务,这些任务往往需要经过一段时间才能完成,例如定时器、AJAX请求、文件I/O请求等,如果这些任务也以同步的方式执行,可以想象一下场景——当某个网络请求迟迟得不到回应,那么后面的代码都没有机会执行,整个应用都像被冻住了一样。

console.log('1') //输出1
alert('模拟网络请求迟迟得不到响应...') //不处理对话框
console.log('2') //无输出,被阻塞

解决办法也很简单,就是不要把这类任务以同步的方式执行,将它们委托给专门负责的机制去处理,然后继续执行后续的代码即可,这种处理任务的方式就是 异步

console.log('1') //输出1
setTimeout(() => {
  alert('模拟网络请求迟迟得不到响应...') //不处理对话框
})
console.log('2') //输出2

所以,异步对于JS而言是非常重要且不可或缺的!


2. JS异步的独特性

JS曾经就是一个服务网页的脚本语言,操作DOM是它唯一的优势,这就导致JS最初就被设计成一个纯粹的单线程语言,一次只能执行一个任务,直接从源头上避免了多线程操作带来的各种安全问题。
单线程导致JS自身是无法实现异步的,那么JS中那些异步任务是怎么完成的呢?答案是依托宿主环境
所谓的宿主环境就是运行JS代码的平台,最常见的两个宿主环境便是浏览器Node.js不要以为单线程是劣势,Node.js之所以具备高并发能力就是单线程的功劳
这里拿浏览器举例,虽然JS语言本身是单线程的,但是浏览器可是多线程的,JS代码又是运行在浏览器中的,所以自然可以借助浏览器来实现异步。
浏览器内核自身是一个进程,它包含了GUI渲染线程JS引擎线程,还有一些专门负责异步任务的线程(就是前文所属的“受委托的机制”),例如负责Timer计时的定时器线程、负责AJAX请求的XHR线程。

有两个非常关键的特性需要知道:
第一个特性是互斥问题GUI渲染线程与JS引擎线程无法同时工作。当浏览器解析到JS代码时,会优先调用JS引擎线程工作,GUI渲染线程将被挂起。

<body>
  <div>content</div>
  <script>
    alert('模拟网络请求迟迟得不到响应...') //不处理对话框,发现content未能被渲染
  </script>
</body>

第二个特性是顺序问题浏览器会先调用JS引擎执行同步代码,然后调用GUI渲染线程触发一次DOM渲染,最后再处理那些已就绪的异步任务。但是有个特殊情况需要留意,由于浏览器显示是有一定的刷新频率的,如果异步任务就绪得太早,DOM渲染的结果还没来得及在屏幕上呈现,异步任务就先被JS引擎执行了

<body>
  <div>content</div>
  <script>
  	setTimeout(() => {
  	  alert('模拟网络请求迟迟得不到响应...') // 不处理对话框,发现content正常渲染
  	}, 1000) // 不可不设置或者设置过短,防止抢先执行
  </script>
</body>

这两个特性更加证明了异步的重要性——JS引擎在同步任务上耗时过久会导致页面无法及时被渲染!

综上所述,JS的异步完全是依托宿主环境实现的,这也是JS异步相比其它动态语言略显错综复杂的原因之一,学习的过程中只有抓住了它的本质才能更好的理解它。

拓展:实际上console.log并不一定总是同步的,正如本节所述,类似这样的API都不是JS自己规定的,而是由宿主环境实现的,所以在某些环境下console.log会被视作异步任务执行。例如在Vue3中setup()里打印输出某些mounted阶段才初始化的内容时,可以在控制台看到值。


3. 传统异步编程

传统的JS异步编程是靠纯粹的回调函数实现的,像定时器、AJAX请求、I/O操作等都属于宏任务,所谓的宏任务就是由宿主环境发起的异步任务,它们的函数调用结构都类似以下这种形式:

setTimeout(callback, timeout)
ajax(url, callback)
readFile(path, callback)

JS在解析这些这类任务的时候,会将其委托给对应的子线程处理,当这些任务就绪时(例如定时器的延时已到、AJAX获取服务端响应、I/O请求得到资源),子线程会将它们的callback添加到宏任务队列中,当JS引擎 函数调用栈 中的任务执行完成后,会调用事件循环器从宏任务队列中拉取这些callback执行,每一次函数调用栈清空后都会尝试触发一次DOM渲染。


4. 回调地狱问题

试想一下,假设现在有一个需求:我们需要向三个URL发送AJAX请求分别获得数据1、数据2、数据3,并将三个数据同时作为参数进行处理。

相信也有人跟我一样曾经想出过这种搞笑的写法:

let data1 = null
let data2 = null
let data3 = null

ajax(url_1, res => {
  data1 = res
})
ajax(url_2, res => {
  data2 = res
})
ajax(url_3, res => {
  data3 = res
})

work(data1, data2, data3)

其实有这种想法真的很正常,它就是一种符合我们思维习惯的同步模式,但是不要忘了AJAX请求是异步任务,work被执行到的时候data1data2data3还都是null呢,
问题所在就是——你必须确保这3个异步任务完成并拿到结果后才能执行work!也就是说不能把work放到同步任务中执行。

可能有人跟我一样曾经想过这样改写:

let data1 = null
let data2 = null
let data3 = null

ajax(url_1, res => {
  data1 = res
})
ajax(url_2, res => {
  data2 = res
})
ajax(url_3, res => {
  data3 = res
})

setTimeout(() => {
  work(data1, data2, data3)
}, 5000)

这种写法能不能成功全凭运气,如果运气好,这三个AJAX请求都在5秒内获取到结果了,那就没啥问题,如果运气不好就凉了。而且,硬编码一个5秒进去也太low了,这个逻辑跟三个AJAX请求也没有任何联系,绝对不是可行的方案。

实际上正常人会这么写,在上一个任务的回调函数里面书写下一个任务

ajax(url_1, data1 => {
  ajax(url_2, data2 => {
    ajax(url_3, data3 => {
      work(data1, data2, data3)
    })
  })
})

这样子逻辑才是顺通的,当第一个AJAX请求拿到数据后再触发第二个AJAX请求,依次类推,当work被执行到的时候,三个数据肯定已经顺利拿到了。
在jQuery那个时代,我们的异步编程几乎都是以这种纯粹回调函数的方式书写的,不过这种写法带来了两个问题:

  1. 回调函数之间形成了嵌套:现在我们的逻辑只有三层,看起来还能够接受,但写过项目的人都知道,一个逻辑稍微复杂的项目,JS代码的各个地方都充斥着无尽的嵌套,导致这个问题的罪魁祸首就是回调函数
  2. 这三个异步任务无法同时发生:如果回调嵌套的中间层会依赖上层的数据,这就必须等待上层得到结果后才能进行下一层,不过我们这段逻辑中这三个AJAX请求彼此互不依赖,仅仅是为了确保work的执行时机正确而被迫写成了递进的形式,没有充分利用执行时间

这种层层嵌套的回调函数被称之为回调地狱,它使得代码变得难以阅读和维护。


5. Promise方案

为了解决回调地狱的问题,社区一直在探讨解决方案,Promise就是一个被ES6官方收录的解决方案,Promise本身是一个构造函数,它的实例默认处于等待态,用户可以使用resolve触发成功态、使用reject触发失败态,状态仅会由等待态转变一次,用户可以通过then指定状态转变时执行的回调函数,它最大的特点就是支持链式调用

我们将上一个例子用Promise进行改造,这是最“朴实无华”的写法:

let data1 = null
let data2 = null
let data3 = null

new Promise((resolve, reject) => {
  ajax(url_1, res => {
  	data1 = res
    resolve()
  })
})
  .then(() => {
    return new Promise((resolve, reject) => {
      ajax(url_2, res => {
        data2 = res
        resolve()
      })
    })
  })
  .then(() => {
    return new Promise((resolve, reject) => {
      ajax(url_3, res => {
        data3 = res
        resolve()
      })
    })
  })
  .then(() => {
    work(data1, data2, data3)
  })

姑且先不谈代码量,可以明显看出,经过Promise改造后的逻辑是链式向下的,这四段逻辑彼此没有嵌套。仔细观察,Promise实例的then方法会返回一个新的Promise实例,你可以在这个新的实例里继续通过then指定回调,只要前一个实例的状态被改变,后一段回调便会执行。
不过这种原始写法太笨拙了,而且还是没有优化三个AJAX任务串行的问题。既然前三段逻辑都返回一个Promise实例,并且都调用了ajax,那么我们可以自己封装一个函数:

function request(url) {
  return new Promise((resolve, reject) => {
    ajax(url, res => {
      resolve(res)
    })
  })
}

Promise有个静态方法Promise.all(iterable),它会执行集合中的所有Promise实例并返回一个新实例,一旦集合中有实例转变为失败态,便会立刻触发返回实例的reject,仅当集合中所有的实例都转变为成功态后,它才会返回成功的实例

Promise
  .all([request(url_1), request(url_2), request(url_3)])
  .then(value => {
    work(...value)
  })

经过上面一系列改造过程,我们感受到了Promise的灵活性,它解决了回调地狱的问题,并且给我们编写异步代码来带了更多可能


6. 微任务的诞生

Promise作为ES6官方内置的异步方案,它使得JS无需依赖宿主环境即可自行发起异步任务了,所以从ES6之后微任务的概念便诞生了,类似Promise这种由JS自身发起的的任务就属于微任务。
既然微任务是JS自己维护的,那么它自然是在DOM渲染前触发的,这就是为何大家常说微任务的执行时机比宏任务要早
我们来看下面这个例子:

document.documentElement.addEventListener(
  'click',
  () => {
  	// 将页面背景修改为粉色
    document.documentElement.style.backgroundColor = 'pink'
    // 宏任务:将页面背景修改为蓝色
    setTimeout(() => {
      console.log(document.documentElement.style.backgroundColor)
      document.documentElement.style.backgroundColor = 'blue'
      alert('在宏任务中模拟阻塞...')
    })
    // 微任务:将页面背景修改为绿色
    Promise.resolve().then(() => {
      console.log(document.documentElement.style.backgroundColor)
      document.documentElement.style.backgroundColor = 'green'
      alert('在微任务中模拟阻塞...')
    })
  },
  false
)

整个过程详细分析:

  1. 鼠标点击事件触发后,首先整个脚本会作为一个整体执行,DOM结构中的背景色会先被赋值为pink(此时还未渲染到页面上),然后setTimeout的任务会被委托给对应的定时器线程处理,最后Promise中then指定的callback会被加入微任务队列
  2. 定时器线程发现定时任务的延时已到,会将callback添加到宏任务队列中,与此同时,由于整个脚本已经执行完毕,JS引擎会调用事件循环器先从微任务队列中拉取callback执行,控制台打印pink(页面空白),然后DOM结构中的背景色被修改为green(页面还是空白),弹框阻塞了JS引擎向下执行
  3. 当我们处理弹框后,JS引擎继续工作,此时它发现微任务队列为空了,JS引擎被挂起,GUI渲染引擎被调用,浏览器触发了一次DOM渲染,页面直接变为绿色
  4. JS引擎恢复工作,开始从宏任务队列中拉取callback执行,控制台打印green(页面是一致的),然后DOM结构中的背景色被修改为了blue(此时页面还是绿色),弹框导致JS引擎暂停
  5. 处理该弹框后,JS引擎发现宏任务队列也没有任务了,便会结束一个事件循环周期,在下一个事件循环周期中页面才被渲染为了蓝色

可以发现,整个过程页面都没有出现粉色,因为DOM渲染是在微任务之后才发生的,那个时候DOM结构中的粉色已经被修改为绿色了。

总结一下:同步代码向下走,遇宏移交子线程,遇微加进微队里,宏任就绪入宏队,微队优先把回调,完工DOM要渲染,最后宏队再运行。


7. 不知名的Generator

人们对代码整洁(偷懒)的追求从未停止过,如果能用易于理解和阅读的简短方式书写代码,谁会喜欢写一串绕来绕去的逻辑呢。
Promise有效解决了回调地狱问题,但是它的书写还是不够优雅。回忆一下本文在回调地狱小节引出的案例:向三个URL发送AJAX请求分别获得数据1、数据2、数据3,并将三个数据同时作为参数进行处理。我在上文也提过,我最开始在对同步异步一无所知的情况下,曾经想过这么写:

ajax(url_1, res => {
  data1 = res
})
ajax(url_2, res => {
  data2 = res
})
ajax(url_3, res => {
  data3 = res
})
work(data1, data2, data3)

我那时觉得这才是正常人理解代码的逻辑吧,这种想法也对也不对,对在于同步的书写方式确实更易于书写,不对在于你不可能让JS把这种任务都作为同步任务执行,那样又回到起点了。
其实我们变相思考下:我们可以在同步代码中,手动指定某些异步任务必须得到结果后才能向下执行不就好啦?

其实ES6还引入了一个叫做生成器的东西Generator,它长得特别像函数,使用function*定义,但是它跟函数差别很大:

  1. 函数调用后会立刻执行并返回return后面的值,生成器被调用后并不会立刻执行,而是返回的是一个可迭代的generator对象,你需要调用next方法才会开始执行
  2. 函数在执行的过程中,遇到return才会把控制权交回主调函数,而生成器执行时一遇到yield就会返回一个包含着value的对象,并且暂停执行,你需要再次调用next方法才会继续

说白了,生成器这个东西仿佛可以保存执行时的状态,我们可以精细化控制它每一段逻辑的执行时机,如果把它和Promise结合起来,我们不就可以尝试用同步的风格书写异步代码了吗?

function* generator() {
  data1 = yield request(url_1)
  data2 = yield request(url_2)
  data3 = yield request(url_3)
  work(data1, data2, data3)
}

这段伪代码看起来很美好,但想真正实现还需要考虑很多细节,例如生成器执行的问题,毕竟生成器函数它不会自动执行,我们要自己去封装自动执行的函数或者引入其它第三方库,这个过程比较繁琐,所以我们很少在实际开发中自己去使用Generator来处理异步,不过了解Generator对于我们学习async/await很有帮助。


8. 终极方案async/await

async/await被称作是JS异步问题的终极解决方案,它可以看作Promise + Generator + 自动执行的语法糖,它力争从语法层面消除回调函数。

  • 当一个函数被async修饰后,它内部就可以使用await表达式了,并且整个函数的返回值会被处理为一个Promise对象
  • await右侧通常接一个返回Promise对象的函数调用,await会阻塞代码的执行并等待Promise对象发生状态转变,如果该对象的状态转变为成功态,await会正常返回它的结果值,如果该对象的状态转变为失败态,则await会抛出异常(相当于Promise.prototype.then
  • await右侧也可以接一个普通的值,await会直接返回该值(相当于Promise.resolve().then
async function fun() {
  data1 = await request(url_1)
  data2 = await request(url_2)
  data3 = await request(url_3)
  work(data1, data2, data3)
}

是不是感觉跟刚才Generator那个伪代码很相似,async/await语法糖让你可以用同步代码的风格去处理异步任务,而且即使几个异步任务间有数据的依赖,也完全不需要去做回调函数的嵌套,可算是既解放了双手又简化了代码。
不过这个例子的中三个request并不互相依赖,可以结合Promise.all进行改写:

async function fun() {
  data = await Promise.all([request(url_1), request(url_2), request(url_3)])
  work(...data)
}

async/await实际上是ES8才正式定义的用法,而且也是基于Promise的,它并没有改变JS异步的本质,JS异步还是依靠回调函数,await所在行后方的整体都可以看作该行的回调。

纵观探索史的后半程,不难发们当下的JS异步编程几乎都是直接或间接围绕Promise进行的,所以学习及模仿Promise源码很有意义!

在这里插入图片描述

二、模仿源码前的构思

渐进式是一种很符合我们思维习惯的学习形式,因为我们是先学会使用Promise,再尝试去模仿它的源码的,所以在正式开始实践之前,我们应该先想清楚几个问题,例如:Promise是什么、它可能有哪些属性,Promise有哪些实例方法、哪些原型方法、哪些静态方法,这些方法是怎么和属性配合的,这些方法的功能会以怎样的逻辑实现等等…

1. 梳理构造流程

  • 使用Promise的起点就是new Promise(),所以Promise自身显然是个构造函数(也可以视作一个
  • Promise实例有三种状态,分别是pending态、fulfilled态和rejected态,所以实例身上应该有一个保存状态信息的属性PromiseState,并且它的初始值应当为pending
  • 构造Promise实例的时候要传入一个回调函数executor,它的形式是(resolve, reject) => {...},其中resolvereject的名字可以随意更换,但是顺序不能乱,我们在函数体中调用的resolvereject实际上是调用Promise提供的两个实例方法
  • resolve一旦调用,Promise实例的状态会被修改为fulfilledreject一旦被调用,Promise实例的状态会被修改为rejected,最重要的一点是——Promise实例的状态仅会转变一次,所以在修改状态前应该先判断当前的状态是否为pending,不是的话说明状态在之前已经被修改过一次了,必须阻止本次修改
  • resolvereject都可以接受一个参数,这个参数作为该次状态转变的结果值,所以Promise身上应当还有一个实例属性PromiseResult用于记录该值
  • 除了调用resolvereject改变实例状态,还可以throw一个异常来触发rejected态,所以我们要用try {...} catch (error) { reject(error) }将执行executor的语句围起来

2. 异步回调的处理

这一块是整个源码模仿过程中最绕的部分,所以我们先感性入手,不考虑太多细节,随着逐层深入再补全细节。

  • 我们可以调用Promise实例身上的then方法指定两个回调函数,形式为.then(value => {...}, reason => {...})(参数名依旧随意),当Promise实例的状态发生转变时,这俩回调的其中一个才会被执行,第一个回调是响应fulfilled态的,所以可以称之为onResolved,第二个回调是响应rejected态的,所以可以称之为onRejected
  • 如果then执行的时候,Promise实例的状态已经发生转变了,那自然不必多说,直接执行onResolved或者onRejected就可以了
  • 但如果then执行的时候,Promise实例还在等待中呢?那肯定不能在then里面立刻执行onResolved或者onRejected了,只能通知Promise实例在之后的时间自己去执行,所以Promise实例身上应该要有一个暂存回调函数的队列,暂定名callbackQueue,它存储的类型是包含onResolvedonRejected的对象
  • 暂存回调函数的队列有了,现在还要让Promise实例自己在状态转变后去执行它们,状态转变就是resolvereject触发的,所以在这两个方法中增加一个迭代的逻辑即可,将队列中的回调函数遍历并执行,如果是resolve被触发那么就执行onResolved,如果是reject被触发那么就执行onRejected
  • then方法执行完毕后会返回一个新的Promise实例,如果用户在回调函数中自己手动return了一个Promise实例,那么返回值就以它这个实例的状态为准,如果return了其它的值,那么该值会被包装进一个fulfilled态的实例并返回,也就是说,我们上述执行onResolved或者onRejected的操作应该在return new Promise((resolve, reject) => {...})里面进行
  • 实际上,我们在使用then时经常不会将两个回调函数都写上,有时候只写onResolved,有时候两个都不写,但是then依旧能够正常工作,这是因为它内部有默认的逻辑,当用户不传onResolved时会匹配一个函数value => value,当用户不传onRejected时会匹配一个函数reason => { throw reason }
  • 关键的一步,我们Promise是跟异步有关的,这些回调函数不能以同步的方式执行,但是我们好像无法轻易模拟微任务(反正我不知道怎么去手工模拟微任务),所以我们用宏任务来替代一下,在每一个执行onResolved或者onRejected的地方都套上setTimeout(() => {...})(虽然有点low,但毕竟只是模拟嘛,哈哈)
  • 最后一步,注意执行onResolved或者onRejected时可能会抛出异常,记得也要用try {...} catch (error) { reject(error) }括起来

3. Promise.all的设计

这是静态方法中相对复杂的一个,虽然跟then完全不是一个级别的,但是也有些细节。

  • Promise.all(iterable)接收一个由Promise实例组成的可迭代对象,这里为了简化我们不考虑做成真正的可迭代对象,直接用普通的数组替代。Promise.all会执行数组里的所有Promise实例,当有一个实例的状态转变为rejected时,会立马返回一个rejected态的新实例,仅当数组中所有实例都转为fulfilled它才会返回fulfilled态的新实例,并且新实例的结果值是数组里所有实例结果值组成的数组
  • Promise.all的设计思路是:我们先定义一个统计成功实例数量的变量count,再定义一个结果数组resultArray,然后循环遍历数组中每一个Promise实例,利用then给其指定两个回调,onResolved要让count++,并且把结果值保存进resultArray,当count === resultArray.length时说明所有实例都成功完成了,可以调用resolve
  • 把结果值保存进resultArray这个操作的设计有点小细节,如果你是用resultArray.push的方式添加值,那样会出点小bug,Promise可是异步解决方案,不是所有的实例都会立刻转变状态的,这种方式生成的结果数组可能是乱序的,所以最好换用数组下标进行赋值
  • 至于onRejectd嘛,直接reject就行了

4. 其它方法设计

整个Promise中最难的几块内容已经搞定了,剩下的这些的内容很简单,算是收尾啦。

  • Promise实例上有个catch方法,能够处理异常穿透(即前面没有捕获的异常由它统一捕获),它的形式是.catch(reason => {...}),实际上你可以把它看成.then(undefined, reason => {...}),所以实现就太简单了,直接调用实例的then并返回即可
  • 静态方法Promise.resolve(value)会对value进行解析并返回一个Promise实例,如果value是一个Promise实例,则返回实例的状态以它为准,如果value就是普通的值,那就包装成fulfilled态的实例并返回
  • 静态方法Promise.reject(reason)会直接返回一个rejected态的Promise实例,结果值就是reason
  • 还有一个静态方法Promise.race(iterable),形式和Promise.all(iterable)很像,它以第一个发生状态转变的实例为准,所以设计也很直观:遍历所有Promise实例并通过then指定onResolvedonRejected,逻辑就是简单的调用rejectresolve
    在这里插入图片描述

三、源码实践环节

经过前面由浅入深的构思,我们基本已经掌握了Promise的设计脉络,由于篇幅的原因,源码实践环节直接贴上了最终源码,使用ES6 class语法糖书写,它的实现过程跟上文描述的是一模一样的,而且添加了很详细的注释。
值得注意的是,本文的设计目标并不是真正的复现Promise源码,而是以简化的方式模拟它的工作流程,像微任务这种过于精细的问题都没有做任何处理,我们普通的开发者学习源码也并不是想自己重新造一个库,而只是加深对知识理解,并且开拓一下自己解决问题的思路而已。

/**
 * 自定义Promise类
 */
class Promise {
  /**
   * 构造方法(初始化实例的基本属性)
   * @param {Function} executor 构造Promise时传入的回调函数
   */
  constructor(executor) {
    //状态属性
    this.PromiseState = 'pending'
    //结果属性
    this.PromiseResult = null
    //异步回调函数队列
    this.callbackQueue = []
    //保存this实例
    const self = this

    //resolve函数
    function resolve(data) {
      //确保状态只转变一次
      if (self.PromiseState !== 'pending') return
      //将状态改为fulfilled
      self.PromiseState = 'fulfilled'
      //设置结果值
      self.PromiseResult = data
      //异步执行回调队列中的resolve函数
      setTimeout(() => {
        self.callbackQueue.forEach(item => {
          item.onResolved(data)
        })
      })
    }

    //reject函数
    function reject(data) {
      //确保状态只转变一次
      if (self.PromiseState !== 'pending') return
      //将状态改为rejected
      self.PromiseState = 'rejected'
      //设置结果值
      self.PromiseResult = data
      //异步执行回调队列中的reject函数
      setTimeout(() => {
        self.callbackQueue.forEach(item => {
          item.onRejected(data)
        })
      })
    }

    try {
      //同步执行executor
      executor(resolve, reject)
    } catch (error) {
      //捕获到异常时直接reject
      reject(error)
    }
  }

  /**
   * Promise.prototype.then(处理实例状态变化后执行的逻辑,支持链式调用)
   * @param {Function} onResolved resolve回调函数
   * @param {Function} onRejected reject回调函数
   * @returns 一个新的Promise实例
   */
  then(onResolved, onRejected) {
    const self = this
    //若用户没有传递onResolved,则为其匹配一个默认函数
    if (typeof onResolved !== 'function') {
      onResolved = value => value
    }
    //若用户没有传递onRejected,则为其匹配一个默认函数
    if (typeof onRejected !== 'function') {
      onRejected = reason => {
        throw reason
      }
    }
    return new Promise((resolve, reject) => {
      //封装通用逻辑(执行onResolved/onRejected并处理结果)
      function callback(type) {
        //执行onResolved/onRejected并保存返回结果
        const result = type(self.PromiseResult)
        //判断返回结果是否是Promise
        if (result instanceof Promise) {
          //是Promise,调用其then方法
          result.then(
            value => {
              resolve(value)
            },
            reason => {
              reject(reason)
            }
          )
        } else {
          //不是Promise,直接将状态改为fulfilled
          resolve(result)
        }
      }
      //若状态已转变为fulfilled时
      if (self.PromiseState === 'fulfilled') {
        try {
          //异步执行onResolved并处理结果
          setTimeout(() => {
            callback(onResolved)
          })
        } catch (error) {
          //捕获到异常时直接reject
          reject(error)
        }
      }
      //若状态已转变为rejected时
      if (self.PromiseState === 'rejected') {
        try {
          //异步执行onRejected并处理结果
          setTimeout(() => {
            callback(onRejected)
          })
        } catch (error) {
          //捕获到异常时直接reject
          reject(error)
        }
      }
      //如果状态还未发生改变,则暂存回调函数
      if (self.PromiseState === 'pending') {
        self.callbackQueue.push({
          onResolved() {
            try {
              //执行onResolved并处理结果
              callback(onResolved)
            } catch (error) {
              //捕获到异常时直接reject
              reject(error)
            }
          },
          onRejected() {
            try {
              //执行onRejected并处理结果
              callback(onRejected)
            } catch (error) {
              //捕获到异常时直接reject
              reject(error)
            }
          }
        })
      }
    })
  }

  /**
   * Promise.prototype.catch(处理异常穿透)
   * @param {Function} onRejected reject回调函数
   * @returns 一个新的Promise实例
   */
  catch(onRejected) {
    return this.then(undefined, onRejected)
  }

  /**
   * Promise.resolve(对参数值解析并返回相应的Promise实例)
   * @param {Object} value 参数值
   * @returns 一个新的Promise实例
   */
  static resolve(value) {
    return new Promise((resolve, reject) => {
      //判断传入参数是否是Promise
      if (value instanceof Promise) {
        //是Promise,调用其then方法
        value.then(
          value => {
            resolve(value)
          },
          reason => {
            reject(reason)
          }
        )
      } else {
        //不是Promise,直接将状态改为fulfilled
        resolve(value)
      }
    })
  }

  /**
   * Promise.reject(返回一个带有拒绝原因的Promise实例)
   * @param {Object} reason 拒绝原因
   * @returns 一个新的Promise实例(必为rejected态)
   */
  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason)
    })
  }

  /**
   * Promise.all(一旦有实例转变为rejected态便会立刻触发拒绝)
   * @param {Array<Promise>} promises 由Promise实例组成的数组
   * @returns 一个新的Promise实例
   */
  static all(promises) {
    return new Promise((resolve, reject) => {
      //计数变量
      let count = 0
      //结果数组
      const resultArray = []
      //遍历每一个Promise实例
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(
          value => {
            //当该实例转变为fulfilled态时加一
            count++
            //将结果值保存进数组
            resultArray[i] = value
            //若所有实例都转变为fulfilled态,则执行resolve
            if (count === promises.length) {
              resolve(resultArray)
            }
          },
          reason => {
            //只要有一个实例转变为rejected态,则直接执行reject
            reject(reason)
          }
        )
      }
    })
  }

  /**
   * Promise.race(取决于第一个发生状态转变的实例)
   * @param {Array<Promise>} promises 由Promise实例组成的数组
   * @returns 一个新的Promise实例
   */
  static race(promises) {
    return new Promise((resolve, reject) => {
      //遍历每一个Promise实例
      for (let i = 0; i < promises.length; i++) {
        //谁先转变状态,结果就由谁决定
        promises[i].then(
          value => {
            resolve(value)
          },
          reason => {
            reject(reason)
          }
        )
      }
    })
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值