手撕源码系列 —— 函子 + 观察者模式 + 状态 = Promise

前言

前段时间太忙,隔了快一个月没写博客,可是 Promise 其实很早以前就已经总结了一波如何实现,可是那个时候纯粹是为了实现而实现,没有去细品其中的一些巧妙设计,直到最近在进行函数式编程相关的知识学习时,无心中在查阅资料的时候发现,PromiseFunctor 竟然有着千丝万缕的关系,这让我决定要从新审视一下本身对 Promise 的认知,因而便有了这篇“老酒新装”的博客。javascript

前置知识

想要彻底阅读并理解这篇博客,我粗略地估算了一下,大概须要如下的一些前置知识,不了解的同窗能够自行先去学习一下:前端

  • 递归的思想
  • ES6Typescript 的基础认知
  • 函数式编程中的函子(Functor)和“函数是一等公民”思想
  • 设计模式中的观察者模式

分步实现 Promise

咱们能够将实现一个 Promise 比做盖一栋楼,经过拆分每一步并解决、理解和记忆,达到很快就能理解它的实现的目的。java

1、打下 Promise 的地基

因为咱们经常使用 Promise 配合 async/await 来实现异步编程,因此先回想一下如何最最基本地使用 Promisegit

new Promise(resolve => {...})
    .then(onFulfilled)
    .then(onFulfilled)

根据上面咱们回忆中的最最基本的 Promise 咱们能够写出如下实现:github

class Promise {
    constructor(executor) {
        const resolve = () => {}
        
        executor(resolve)
    }
    
    then = onFulfilled => new Promise(...)
}

好的,那咱们的第一步到这里就结束了,是否是十分简单,彻底不须要什么成本就能理解并记忆下来。shell

2、加入原材料 函子 和 观察者模式

这一步,咱们开始往里加东西了。对于加的东西,咱们也必须了解是什么,因此先来看下两个原材料的基本概念。npm

函子

因为在 前置知识 里提到,相信你们已经对它有所了解,一个基本的函子咱们能够写成如下实现:编程

class Functor {
    static of = value => new Functor(this.value)
    
    constructor(value) {
        this.value = value
    }
    
    map = fn => Functor.of(fn(this.value))
}

为了方便映射到 Promise 的实现中,改成如下写法:segmentfault

class Functor {
    constructor(value) {
        this.value = value
    }
    
    map = fn => new Functor(fn(this.value))
}

而后结合到函子的一些特性:设计模式

const plus = x + y => x + y
const plusOne = x => plus(x, 1)

new Functor(100)
    .map(plusOne)
    .map(plusOne) // { value: 102 }

这个是时候再发挥咱们从小被培养的找规律能力,咱们发现:

  1. 二者的结构相似,拥有一个方法对内部的数据进行操做
  2. 二者都可进行链式调用

经过以上两点能够获得一个结论,之因此引入函子,能够解决链式调用的问题,可是光有一个函子不够呀,函子只能实现同步的链式调用,这时候另一个原材料观察者模式就出场了。

观察者模式

先看一个简单的观察者模式实现:

class Observer {
  constructor() {
    this.callbacks = []

    this.notify = value => {
      this.callbacks.forEach(observe => observe(value))
    }
  }

  subscribe = observer => {
    this.callbacks.push(observer)
  }
}

这时候聪明的人一下就发现了,这个 notifysubscribe 不就是 resolvethen 嘛!

俺のターン!ドロー!魔法発动!

c21f67221889af25edb36826285ce8aa_hd.jpg

ターンエンド!

class Promise {
    constructor(executor) {
        this.value = undefined
        this.callbacks = []
        
        // 至关于 notify
        const resolve = value => {
            this.value = value
            this.callbacks.forEach(callback => callback())
        }
        
        executor(resolve)
    }
    
    // 至关于 subscribe 或 map
    then = onFulfilled => new Promise(resolve => {
        this.callbacks.push(() => resolve(onFulfilled(this.value)))
    })
}

融合后的初级 Promise 已经具备异步链式调用的能力了好比:

const promise = new Promise(resolve => {
    setTimeout(() => {
        resolve(100)
    }, 500)
})
    .map(plusOne)
    .map(plusOne)
    // { value: 102 }

可是当咱们进行一些骚操做时,依然会出问题:

const promise = new Promise(resolve => {
    setTimeout(() => {
        resolve(100)
        resolve(1000)
    }, 500)
})
    .map(plusOne)
    .map(plusOne)
    // { value: 1002 }

为了解决这个问题,咱们还须要一个原材料状态

篇幅有限,这一部分更细致的转换过程,个人 repo 都有记录。

3、加入原材料 状态

众所周知,<span style="text-decoration-line: line-through">“青眼究极龙须要三条青眼白龙”</span>,为了解决上一部分留下的问题,这一部分,须要给 Promise 加入状态这个原材料。

class Promise {
  static PENDING = 'PENDING'
  static FULFILLED = 'FULFILLED'

  constructor(executor) {
    this.value = undefined
    this.callbacks = []
    this.status = Promise.PENDING

    // 一系列操做(状态的改变,成功回调的执行)
    const resolve = value => {
      // 只有处于 pending 状态的 promise 能调用 resolve
      if (this.status === Promise.PENDING) {
        // resolve 调用后,status 转为 fulfilled
        this.status = Promise.FULFILLED
        // 储存 fulfilled 的终值
        this.value = value
        // 一旦 resolve 执行,调用储存在回调数组里的回调
        this.callbacks.forEach(callback => callback())
      }
    }

    executor(resolve)
  }

  then = onFulfilled =>
    new Promise(resolve => {
      // 当 status 为执行态(Fulfilled)时
      if (this.status === Promise.FULFILLED) {
        resolve(onFulfilled(this.value))
      }
      // 当 status 为 Pending 时
      if (this.status === Promise.PENDING) {
        // 将 onFulfilled 存入回调数组
        this.callbacks.push(() => resolve(onFulfilled(this.value)))
      }
    })
}

至此,经过三大原材料构建出的 Promise 就完成了,固然,还有不少功能没有实现,<span style="text-decoration-line: line-through">鲁迅曾经说过:</span>“要站在巨人的肩膀上看问题。”,下一步,就须要 Promise/A+ 规范来来帮助咱们实现一个具备完整功能的 Promise

4、打开设计图纸 Promise/A+ 规范

剑来! Promise/A+ 规范,接下来的操做,须要跟着它一步一步进行。

一、加入拒绝态以及处理(reject and onRejected)

其实这一步不用规范咱们也知道,Promise 拥有的终态fulfilledrejected 两种,因此要把剩下的 rejected 以及一些相关操做给补上。

class Promise {
  ......
  static REJECTED = 'REJECTED'
  
  constructor(executor) {
    this.value = undefined
    this.reason = undefined
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []
    this.status = PromiseFunctorWithTwoStatus.PENDING

    // 成功后的一系列操做(状态的改变,成功回调的执行)
    const resolve = value => {
        ......
    }
    
    // 失败后的一系列操做(状态的改变,失败回调的执行)
    const reject = reason => {
      // 只有处于 pending 状态的 promise 能调用 resolve
      if (this.status === Promise.PENDING) {
        // reject 调用后,status 转为 rejected
        this.status = Promise.REJECTED
        // 储存 rejected 的拒因
        this.reason = reason
        // 一旦 reject 执行,调用储存在失败回调数组里的回调
        this.onRejectedCallbacks.forEach(onRejected => onRejected())
      }
    }

    executor(resolve, reject)
  }

  then = (onFulfilled, onRejected) =>
    new Promise(resolve => {
      // 当 status 为执行态(Fulfilled)时
      ......
      
      // 当 status 为拒绝态(Rejected)时
      if (this.status === PromiseFunctorWithTwoStatus.REJECTED) {
        reject(onRejected(this.reason))
      }
      
      // 当 status 为 Pending 时
      if (this.status === Promise.PENDING) {
        // 将 onFulfilled 存入回调数组
        this.onFulfilledCallbacks.push(() => resolve(onFulfilled(this.value)))
        // 将 onRejected 存入失败回调数组
        this.onRejectedCallbacks.push(() => reject(onRejected(this.reason)))
      }
    })
}

二、加入核心 resolvePromise 方法实现解决过程

Promise 解决过程是一个抽象的操做,其需输入一个 promise 和一个值,咱们表示为 [[Resolve]](promise, x),若是 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;不然其用 x 的值来执行 promise 。

这种 thenable 的特性使得 Promise 的实现更具备通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法便可;这同时也使遵循 Promise/A+ 规范的实现能够与那些不太规范但可用的实现能良好共存。

根据规范的描述,咱们依照他给的实现步骤,写出代码实现:

class Promise {
    ......
    static resolvePromise = (anotherPromise, x, resolve, reject) => {
      // 若是 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
      // 运行 [[Resolve]](promise, x) 需遵循如下步骤:
        
      // 若是 promise 和 x 指向同一对象,以 TypeError 为拒因拒绝执行 promise 以防止循环引用
      if (anotherPromise === x) {
        return reject(new TypeError('Chaining cycle detected for promise'))
      }
      
      // 若是 x 为 Promise ,则使 promise 接受 x 的状态
      if (x instanceof Promise) {
          x.then(
          // 若是 x 处于执行态,用相同的值执行 promise
          value => {
            return Promise.resolvePromise(anotherPromise, value, resolve, reject)
          },
          // 若是 x 处于拒绝态,用相同的拒因拒绝 promise
          reason => {
            return reject(reason)
          }
        )
        // 若是 x 为对象或者函数
      } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
          let called = false
          try {
              // 把 x.then 赋值给 then(这步咱们先是存储了一个指向 x.then 的引用,而后测试并调用该引用,以免屡次访问 x.then 属性。这种预防措施确保了该属性的一致性,由于其值可能在检索调用时被改变。)
              const then = x.then
              // 若是 then 是函数,将 x 做为函数的做用域 this 调用之。传递两个回调函数做为参数,
              if (typeof then === 'function') {
                then.call(
                    x,
                    // 第一个参数叫作 resolvePromise ,
                    value => {
                        // 若是 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了屡次,则优先采用首次调用并忽略剩下的调用
                        if (called) {
                            return 
                        }
                        called = true
                        // 若是 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
                        return Promise.resolvePromise(
                          anotherPromise,
                          value,
                          resolve,
                          reject
                        )
                    },
                    // 第二个参数叫作 rejectPromise
                    reason => {
                        // 若是 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了屡次,则优先采用首次调用并忽略剩下的调用
                        if (called) {
                          return
                        }
                        called = true
                        // 若是 rejectPromise 以拒因 r 为参数被调用,则以拒因 r 拒绝 promise
                        return reject(reason)
                    }
                )
              } else {
                  //若是 then 不是函数,以 x 为参数执行 promise
                  return resolve(x)
              }
          } catch (error) {
              // 若是调用 then 方法抛出了异常 e, 若是 resolvePromise 或 rejectPromise 已经被调用,则忽略之
              if (called) {
                  return
              }
              called = true
              // 若是取 x.then 的值时抛出错误 e ,则以 e 为拒因拒绝 promise
              return reject(error)
          }
      } else {
          // 若是 x 不为对象或者函数,以 x 为参数执行 promise
          return resolve(x)
      }
    }
    ......
}

同时,咱们要对以前的 Promise 里的 resolve 方法进行改造:

class Promise {
    ......
    constructor(executor) {
        ......
        // 成功后的一系列操做(状态的改变,成功回调的执行)
        const resolve = x => {
          const __resolve = value => {
            // 只有处于 pending 状态的 promise 能调用 resolve
            if (this.status === Promise.PENDING) {
              // resolve 调用后,status 转为 fulfilled
              this.status = Promise.FULFILLED
              // 储存 fulfilled 的终值
              this.value = value
              // 一旦 resolve 执行,调用储存在成功回调数组里的回调
              this.onFulfilledCallbacks.forEach(onFulfilled => onFulfilled())
            }
          }
          return Promise.resolvePromise.call(this, this, x, __resolve, reject)
        }
        ......
    }
    ......
}
class Promise {
    ......
    then = (onFulfilled, onRejected) => {
        // then 方法必须返回一个 promise 对象
        const anotherPromise = new Promise((resolve, reject) => {
          // 封装处理链式调用的方法
          const handle = (fn, argv) => {
            // 确保 onFulfilled 和 onRejected 方法异步执行
            setTimeout(() => {
              try {
                const x = fn(argv)
                return Promise.resolvePromise(anotherPromise, x, resolve, reject)
              } catch (error) {
                return reject(error)
              }
            })
          }
          // 当 status 为执行态(Fulfilled)时
          if (this.status === Promise.FULFILLED) {
            // 则执行 onFulfilled,value 做为第一个参数
            handle(onFulfilled, this.value)
          }
          // 当 status 为拒绝态(Rejected)时
          if (this.status === Promise.REJECTED) {
            // 则执行 onRejected,reason 做为第一个参数
            handle(onRejected, this.reason)
          }
    
          // 当 status 为 Pending 时
          if (this.status === Promise.PENDING) {
            // 将 onFulfilled 存入成功回调数组
            this.onFulfilledCallbacks.push(() => {
              handle(onFulfilled, this.value)
            })
            // 将 onRejected 存入失败回调数组
            this.onRejectedCallbacks.push(() => {
              handle(onRejected, this.reason)
            })
          }
        })
    
        return anotherPromise
    }
    ......
}

三、加入 其它方法 完善周边

Promise 的主体已经写好了,接下来要实现其余的一些辅助方法来完善它。

  • catch
catch = onRejected => {
    return this.then(null, onRejected)
}
  • finally
finally = fn => {
    return this.then(
        value => {
            setTimeout(fn)
            return value
        },
        reason => {
            setTimeout(fn)
            throw reason
        }
    )
}
  • resolve
static resolve = value => new Promise((resolve, reject) => resolve(value))
  • reject
static reject = reason => new Promise((resolve, reject) => reject(reason))
  • all
static all = promises => {
    if (!isArrayLikeObject(promises)) {
      throw new TypeError(
        `${
          typeof promises === 'undefined' ? '' : typeof promises
        } ${promises} is not iterable (cannot read property Symbol(Symbol.iterator))`
      )
    }
    
    // 实现的 promise 基于 macroTask 的 setTimeout 实现,须要 async/await 调节执行顺序
    // 原生的 promise 基于 microTask 实现,执行顺序是正确的,不须要 async/await
    return new Promise(async (resolve, reject) => {
      const result = []

      for (const promise of promises) {
        await Promise.resolve(promise).then(resolvePromise, rejectPromise)
      }

      return resolve(result)

      function resolvePromise(value) {
        if (value instanceof Promise) {
          value.then(resolvePromise, rejectPromise)
        } else {
          result.push(value)
        }
      }

      function rejectPromise(reason) {
        return reject(reason)
      }
    })
}
  • race
static race = promises => {
    if (!isArrayLikeObject(promises)) {
      throw new TypeError(
        `${
          typeof promises === 'undefined' ? '' : typeof promises
        } ${promises} is not iterable (cannot read property Symbol(Symbol.iterator))`
      )
    }
    
    return new Promise((resolve, reject) => {
      for (const promise of promises) {
        Promise.resolve(promise).then(
          value => resolve(value),
          reason => reject(reason)
        )
      }
    })
}

四、加入一些健壮性代码

这一部分基本上属于修修补补了,增强 Promise 的健壮性

  • 校验 executor
constructor(executor) {
    // 参数校验
    if (typeof executor !== 'function') {
      throw new TypeError(`Promise resolver ${executor} is not a function`)
    }
}
  • 利用 Maybe函子 的思想,校验 onFulfilledonRejected
then = (onFulfilled, onRejected) => {
    // 若是 onFulfilled 不是函数,其必须被“忽略”
    onFulfilled =
      typeof onFulfilled === 'function' ? onFulfilled : value => value

    // 若是 onFulfilled 不是函数,其必须被“忽略”
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : error => {
            throw error
          }
}

5、装修为 Typescript 风格

这一部分就不写上来了,repo 里有记录。

6、测试是否符合 Promise/A+ 规范

咱们经过一个库来检测写好的 Promise

添加须要的胶水代码:

class Promise {
    ......
    static defer = () => {
        let dfd: any = {}
        dfd.promise = new Promise((resolve, reject) => {
          dfd.resolve = resolve
          dfd.reject = reject
        })
        return dfd
    }

    static deferred = Promise.defer
    ......
}
npm i promises-aplus-tests -D

npx promises-aplus-tests promise.js

QQ20191215-201043.png

总结

最近在翻阅资料的过程当中,真实地感悟到什么是“温故而知新”和“前端的知识虽然杂可是都有联系”。原本 Promise 的实现都被写烂了,可是在学习函数式编程的时候竟然又绕回来了,这种感受实在奇妙,让人不由佩服第一个产生 Promise 想法的人。Promise 将 函子(functor)观察者模式
相结合,加以 状态Promise 的解决过程 进行改造,最终得以实现一个异步解决方案。

篇幅有限,不免一些错误,欢迎探讨和指教~
附一个 GitHub 完整的 repo 地址:https://github.com/LazyDuke/ts-promise-from-functor-observer

后记

这是一个系列,系列文章:

相关文章
相关标签/搜索