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

  • 时间:2020-04-24 13:48 作者:懒成铁 来源: 阅读:10
  • 扫一扫,手机访问
摘要:前言前段时间太忙,隔了快一个月没写博客,但是 Promise 其实很早之前就已经总结了一波如何实现,但是那个时候纯粹是为了实现而实现,没有去细品其中的少量巧妙设计,直到最近在进行函数式编程相关的知识学习时,无意中在查阅资料的时候发现,Promise 和 Functor 居然有着千丝万缕的关系,这让我

前言

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

前置知识

想要完全阅读并了解这篇博客,我粗略地估算了一下,大概需要以下的少量前置知识,不理解的同学可以自行先去学习一下:

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

分步实现 Promise

我们可以将实现一个 Promise 比作盖一栋楼,通过拆分每一步并处理、了解和记忆,达到很快就能了解它的实现的目的。

一、打下 Promise 的地基

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

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

根据上面我们回忆中的最最基本的 Promise 我们可以写出以下实现:

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

好的,那我们的第一步到这里就结束了,是不是十分简单,完全不需要什么成本就能了解并记忆下来。

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

这一步,我们开始往里加东西了。对于加的东西,我们也必需理解是什么,所以先来看下两个原材料的基本概念。

函子

因为在 前置知识 里提到,相信大家已经对它有所理解,一个基本的函子我们可以写成以下实现:

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

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

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

而后结合到函子的少量特性:

const plus = x + y => x + yconst plusOne = x => plus(x, 1)new Functor(100)    .map(plusOne)    .map(plusOne) // { value: 102 }

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

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

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

观察者模式

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

class Observer {  constructor() {    this.callback = []    this.notify = value => {      this.callback.forEach(observe => observe(value))    }  }  subscribe = observer => {    this.callback.push(observer)  }}

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

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

c21f67221889af25edb36826285ce8aa_hd.jpg

ターンエンド!

class Promise {    constructor(executor) {        this.value = undefined        this.callback = []                // 相当于 notify        const resolve = value => {            this.value = value            this.callback.forEach(callback => callback())        }                executor(resolve)    }        // 相当于 subscribe 或者 map    then = onFulfilled => new Promise(resolve => {        this.callback.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 都有记录。

三、加入原材料 状态

众所周知,<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

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

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

1、加入拒绝态以及解决(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)))      }    })}

2、加入核心 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    }    ......}

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

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)        )      }    })}

4、加入少量健壮性代码

这一部分基本上属于修修补补了,增强 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          }}

五、装修为 Typescript 风格

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

六、测试能否符合 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 -Dnpx promises-aplus-tests promise.js
QQ20191215-201043.png

总结

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

篇幅有限,难免少量错误,欢迎讨论和指教~
附一个 GitHub 完整的 repo 地址: LazyDuke/ts-promise-from-functor-observer

后记

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

  • 手撕源码系列 —— lodash 的 debounce 与 throttle

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

  • 手撕源码系列 —— 浅拷贝和深拷贝的完全实现(未完成)

  • 手撕源码系列 —— 老生常谈的call、apply、bind和new(未完成)

  • 全部评论(0)
手机二维码手机访问领取大礼包
返回顶部