美文网首页Web前端之路
手撕源码系列 —— 函子 + 观察者模式 + 状态 = Prom

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

作者: 懒成铁 | 来源:发表于2019-12-15 21:19 被阅读0次

    前言

    前段时间太忙,隔了快一个月没写博客,但是 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 + 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 都有记录。

    三、加入原材料 状态

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

    后记

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

    相关文章

      网友评论

        本文标题:手撕源码系列 —— 函子 + 观察者模式 + 状态 = Prom

        本文链接:https://www.haomeiwen.com/subject/mwplnctx.html