手写一个简易的Promise

作者: JaniceZD | 来源:发表于2020-07-27 16:36 被阅读0次

    1. 简述 Promise

    所谓 Promise,简单来说,就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
    Promise 对异步调用进行封装,是一种异步编程的解决方案。
    从语法上来说,Promise 是一个对象,从它可以获取异步操作的消息。

    1.1 解决什么问题

    有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,即回调地狱。

    1.2 优点
    • 减少缩进
      让回调函数变成了规范的链式写法,程序流程可以看得很清楚。
    改写前:
    f1( xxx , function f2(a){
      f3( yyy , function f4(b){
          f5( a + b , function f6(){})
      })
    })
    
    改写后:
    f1(xxx)
       .then(f2)    // f2 里面调用f3
       .then(f4)    // f4 里面调用f5,注意,f2 的输出作为 f4 的输入,即可将 a 传给 f4
       .then(f6)
    
    • 消灭 if (error)的写法
      为多个回调函数中抛出的错误,统一指定处理方法。

    而且,Promise 还有一个传统写法没有的好处:它的状态一旦改变,无论何时查询,都能得到这个状态。

    1.3 用法
    function fn(){
      //new Promise 接受一个函数,返回一个Promise实例
      return new Promise(( resolve, reject ) => {
           resolve()   // 成功时调用
           reject()     // 失败时调用
      })
    } 
    
    fn().then(success, fail).then(success2, fail2)
    

    new Promise 接受一个函数,返回一个 Promise 实例

    1.4 完整API

    Promise是一个类

    • JS里类是特殊的函数
    • 类属性:length(可以忽略)
      永远是1,因为构造函数只接受一个参数
    • 类方法:all / allSettled / race / reject/ resolve
    • 对象属性:then / finally / catch
    • 对象内部属性:state = pending / fulfilled / rejected

    API 的规则是? Promise / A+规格文档 (JS 的 Promise的公开标准,中文翻译 笔者不保证其准确性)

    1.5 其他

    Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
    状态具有不受外界影响和不可逆2个特点。

    • 不受外界影响
      指只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

    • 不可逆
      一旦状态改变,就不会再变化,会一直保持这个结果,称为 resolved(已定型),任何时候都可以得到这个结果。

    2. 写之前的准备工作

    2.1 创建目录
    promise-demo
        src
            promise.ts
        test
            index.ts
    
    2.2 测试驱动开发

    按照规范文档写测试用例,
    测试失败 -> 改代码 -> 测试成功 -> 加测试 -> ...

    引入chaisinon (测试框架)
    普通的测试用 chai就够用了, sinon是用于测试函数的库。

    • chai 安装步骤
    yarn global add ts-node mocha
    
    //初始化
    yarn init -y
    
    yarn add chai mocha --dev
    
    //添加TypeScript的类型声明文件
    yarn add @types/chai @types/mocha --dev
    
    //为了使用 yarn test 将以下两个安装到本地
    yarn add --dev ts-node
    yarn add --dev typescript
    

    修改 package.json文件:
    添加 test命令,这样就不用每次执行的时候都用 mocha -r ts-node/register test/**/*.ts命令,可以直接使用 yarn test 进行测试。

    "scripts": {
        "test": "mocha -r ts-node/register test/**/*.ts"
      },
    
    • sinon 安装步骤
    yarn add sinon sinon-chai --dev
    yarn add @types/sinon @types/sinon-chai --dev
    

    3. 具体实现

    3.1 new Promise() 必须接受一个函数作为参数

    测试代码:

    import * as chai from "chai"
    import Promise from "../src/promise"
    
    const assert = chai.assert
    
    describe("Promise", () => {
      it("是一个类", () => {
        assert.isFunction(Promise)
        assert.isObject(Promise.prototype)
      })
      it("new Promise() 如果接受的不是一个函数就会报错", () => {
        //assert.thow(fn)的作用:如果fn报错,控制台就不报错;如果fn不报错,控制台就报错。
        //即,预测fn会报错
        assert.throw(() => {
          // @ts-ignore
          new Promise()
        })
        assert.throw(() => {
          //@ts-ignore
          new Promise(1)
        })
        assert.throw(() => {
          //@ts-ignore
          new Promise(false)
        })
      })
    })
    

    assert.thow(fn)的作用:如果 fn报错,控制台就不报错;如果 fn不报错,控制台就报错。
    即,预测 fn 会报错。

    实现代码:

    class Promise2 {
      constructor(fn) {
        if (typeof fn !== "function") {
          throw new Error("只接受函数作为参数!")
        }
      }
    }
    export default Promise2
    

    测试通过。

    测试结果.PNG
    3.2 new Promise(fn) 会生成一个对象,对象有 then 方法
    test/index.ts
    
    it("new Promise(fn)会生成一个对象,对象有 then 方法", () => {
        const promise = new Promise(() => { })
        assert.isObject(promise)
        assert.isFunction(promise.then)
     })
    
    src/promise.ts
    
    添加
    then() {}
    
    3.3 new Promise(fn)中的 fn 会立即执行

    如何判断一个函数会立即执行? => sinon提供了简便的方法

    使用sinon :

    import * as sinon from "sinon"
    import * as sinonChai from "sinon-chai"
    
    chai.use(sinonChai)
    
    it("new Promise(fn)中的 fn 会立即执行", () => {
       //sinon提供了一个假的函数,这个假的函数知道自己有没被调用
        let fn = sinon.fake()
        new Promise(fn)
       //如果这个函数被调用了,called 属性就为 true
        assert(fn.called)
     })
    
    3.4 new Promise(fn)中的 fn 执行的时候必须接受 resolve 和 reject 两个函数
    it("new Promise(fn)中的 fn 执行的时候必须接受 resolve 和 reject 两个函数", done => {
        new Promise((resolve, reject) => {
          assert.isFunction(resolve)
          assert.isFunction(reject)
          done()
        })
      })
    

    关于done :因为有可能这两个语句根本没有执行,测试也会通过,所以使用 done 。用于保证 只有在运行 assert.isFunction(resolve); assert.isFunction(reject)之后才会结束这个测试用例。

    3.5 promise.then(success)中的 success 会在 resolve 被调用的时候执行
    it("promise.then(success)中的 success 会在 resolve 被调用的时候执行", done => {
        let success = sinon.fake()
        const promise = new Promise((resolve, reject) => {
          assert.isFalse(success.called)
          resolve()
          //先等resolve里的success执行
          setTimeout(() => {
            assert.isTrue(success.called)
            done()
          })
        })
        promise.then(success)
      })
    

    then的时候,是先把 success 保存下来,等fn 调用 resolve的时候,resolve就会调用 success。(异步调用)
    resolve需要先等一会,等 success先传入。

    class Promise2 {
      succeed = null
      constructor(fn) {
        if (typeof fn !== "function") {
          throw new Error("只接受函数作为参数!")
        }
        fn(this.resolve.bind(this), this.reject.bind(this))
      }
      resolve() {
        nextTick(() => {
          this.succeed()
        })
      }
      reject() {
    
      }
      then(succeed) {
        this.succeed = succeed
      }
    }
    

    promise.then(nulll,fail) 处的代码类似,不再说明。

    3.6 参考文档写测试用例

    promisethen 方法接收两个参数:

    promise.then(onFulfilled, onRejected)
    
    • onFulfilledonRejected 都是可选的参数,此外,如果参数不是函数,必须忽略
    then(succeed?, fail?) {
        if (typeof succeed === "function") {
          this.succeed = succeed
        }
        if (typeof fail === "function") {
          this.fail = fail
        }
      }
    
    • 如果 onFulfilled 是函数:
      此函数必须在 promise 完成(fulfilled)后被调用,并把 promise 的值(resolve接收的参数)作为onFulfilled它的第一个参数;
      此函数不能被调用超过一次
    resolve(result) {
        if (this.state !== "pending") return;
        this.state = "fulfilled"
        nextTick(() => {
          if (typeof this.succeed === "function") {
            this.succeed(result)
          }
        })
      }
    

    onRejected 类似,不再说明。

    • then 可以在同一个 promise 里被多次调用
      promise变为 fulfilled ,各个相应的 onFulfilled 回调 必须按照最原始的 then 顺序来执行
      即传的是 0 1 2,调用的时候的顺序就是0 1 2

    将目前的代码进行修改,目前的 then只保存一个 succeed 和 一个 fail,但实际上有可能会调用多次。

      resolve(result) {
        if (this.state !== "pending") return;
        this.state = "fulfilled"
        nextTick(() => {
          //遍历callbacks,调用所有的handle[0]
          this.callbacks.forEach(handle => {
            if (typeof handle[0] === "function") {
              handle[0].call(undefined, result)
            }
          })
    
        })
      }
      then(succeed?, fail?) {
        const handle = []
        if (typeof succeed === "function") {
          handle[0] = succeed
        }
        if (typeof fail === "function") {
          handle[1] = fail
        }
        //把函数推到 callbacks 里面
        this.callbacks.push(handle)
      }
    
    • then必须返回一个promise (便于使用链式调用)
      需要创建新的 Promise 实例来对第二个then 中接收的 succeedfail进行存储并执行
      在原本的 resolvereject 函数中,执行第二个 Promise 实例的resolve方法
      参数传递
    it("2.2.7 then必须返回一个promise", done => {
        const promise = new Promise((resolve, reject) => {
          resolve()
        })
        const promise2 = promise.then(() => "成功", () => { })
        assert(promise2 instanceof Promise)
        promise2.then(result => {
          assert.equal(result, "成功")
          done()
        })
      })
    
    resolve(result) {
        if (this.state !== "pending") return;
        this.state = "fulfilled"
        nextTick(() => {
          //遍历callbacks,调用所有的handle[0]
          this.callbacks.forEach(handle => {
            let x
            if (typeof handle[0] === "function") {
              x = handle[0].call(undefined, result)
            }
            handle[2].resolve(x)
          })
        })
      }
    
      then(succeed?, fail?) {
        const handle = []
        if (typeof succeed === "function") {
          handle[0] = succeed
        }
        if (typeof fail === "function") {
          handle[1] = fail
        }
        handle[2] = new Promise2(() => { })
        //把函数推到 callbacks 里面
        this.callbacks.push(handle)
        return handle[2]
      }
    

    再添加错误处理进行完善。

    4. 手写Promise完整代码

    class Promise2 {
      state = "pending"
      callbacks = []
    
      constructor(fn) {
        if (typeof fn !== "function") {
          throw new Error("只接受函数作为参数!")
        }
        fn(this.resolve.bind(this), this.reject.bind(this))
      }
      resolve(result) {
        if (this.state !== "pending") return;
        this.state = "fulfilled"
        nextTick(() => {
          //遍历callbacks,调用所有的handle[0]
          this.callbacks.forEach(handle => {
            let x
            if (typeof handle[0] === "function") {
              try {
                x = handle[0].call(undefined, result)
              } catch (error) {
                handle[2].reject(error)
              }
            }
            handle[2].resolve(x)
          })
        })
      }
      reject(reason) {
        if (this.state !== "pending") return;
        this.state = "rejected"
        nextTick(() => {
          //遍历callbacks,调用所有的handle[1]
          this.callbacks.forEach(handle => {
            let x
            if (typeof handle[1] === "function") {
              try {
                x = handle[1].call(undefined, reason)
              } catch (error) {
                handle[2].reject(error)
              }
            }
            handle[2].resolve(x)
          })
        })
      }
      then(succeed?, fail?) {
        const handle = []
        if (typeof succeed === "function") {
          handle[0] = succeed
        }
        if (typeof fail === "function") {
          handle[1] = fail
        }
        handle[2] = new Promise2(() => { })
        //把函数推到 callbacks 里面
        this.callbacks.push(handle)
        return handle[2]
      }
    }
    
    export default Promise2
    
    function nextTick(fn) {
      if (process !== undefined && typeof process.nextTick === "function") {
        return process.nextTick(fn)
      } else {
        var counter = 1
        var observer = new MutationObserver(fn)
        var textNode = document.createTextNode(String(counter))
        observer.observe(textNode, {
          characterData: true
        });
    
        counter = counter + 1
        textNode.data = String(counter)
      }
    }
    

    代码地址可查看:这里

    相关文章

      网友评论

        本文标题:手写一个简易的Promise

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