函数式编程与 JS 异步编程、手写 Promise学习笔记

645 阅读18分钟

函数式编程

函数式编程的本质

  • 根据输入通过某种运算获得相应的输出,程序开发过程中会涉及很多输入和输出的函数
  • 函数式编程中的函数指的不是程序中的函数(方法),而是数学中的函数既映射关系,例如:y=sin(x),x和y的关系
  • 相同的输入始终会得到相同的输出(纯函数)
  • 函数式编程用来描述数据(函数)之间的映射

为什么要学习函数式编程

  • 函数式编程是随着react的流行受到越来越多的关注 react中的高阶组件使用了高阶函数来实现,高阶函数是函数式编程的一个特性,react并不是纯函数式的
  • vue3开始拥抱函数式编程
  • 函数式编程可以抛弃this
  • 打包过程中可以更好的利用tree shaking过滤无用代码
  • 方便测试,方便并行处理
  • 有很多库可以帮助我们进行函数式开发:lodash、underscore、ramda

函数是一等公民

  • 函数可以存储在变量中
  • 函数可以作为参数
  • 函数可以作为返回值
  • 在JavaScript中函数就是一个普通对象

高阶函数

  • 抽象可以帮我们屏蔽细节,只需要关注与我们的目标

  • 高阶函数是用来抽象通用的问题

  • 常用的高阶函数

    • foreach、map、filter、every、some、find、reduce、sort、some、every......

闭包

  • 函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包
  • 概念:可以在另外一个作用域中调用一个函数的内部并访问到该函数的作用域中的成员
  • 本质:函数在执行的时候会放到一个执行栈上当函数执行完毕之后会在执行栈上移除,但是堆上的作用域成员因为被外部引用不能释放,因此内部函数依然可以访问外部函数的成员
// 示例
function makeFn(){
   let msg = "Hello function"
   return function(){
        console.log(msg)
    }
}

const fn = makeFn()
fn()

纯函数

  • 纯函数:相同的输入永远得到相同的输出,而且没有任何可观察的副作用

    • 不依赖外部状态

    • 纯函数就类似数学中的函数(用来描述输入和输出之间的关系),y=f(x)

    • 函数式编程不会保留计算中间的结果,所以变量是不可变的(无状态的)

    • 我们可以把一个函数的执行结果交给另外一个函数去处理

      /**
       * 纯函数和不纯的函数
       * slice() /splice
       */
      
      let array = [1, 2, 3, 4, 5]
      
      //纯函数(相同的输入得到相同的输出)
      console.log(array.slice(0, 3)) //输出:[ 1, 2, 3 ]
      console.log(array.slice(0, 3)) //输出:[ 1, 2, 3 ]
      console.log(array.slice(0, 3)) //输出:[ 1, 2, 3 ]
      
      //不纯的函数 (改变了原来的数组,相同的输入没有得到相同的输出)
      console.log(array.splice(0, 3)) //输出:[ 1, 2, 3 ]
      console.log(array.splice(0, 3)) //输出:[ 4,5 ]
      console.log(array.splice(0, 3)) //输出:[ ]
      
      //纯函数
      function add(a, b) {
          return a + b
      }
      
      //函数式编程一定要有输出和输出,而且相同的输入永远得到相同输出
      console.log(add(1, 2)) //输出:3
      console.log(add(1, 2)) //输出:3
      console.log(add(1, 2)) //输出:3
      
      
  • lodash是一个纯函数的库,提供了对数组、数字、对象、字符串、函数等操作的一些方法

    • 纯函数的代表:现代化的实用的JavaScript的库,它提供了模块化,高性能和一些附加的功能
  • 纯函数的优点

    • 可缓存
      • 因为纯函数对于相同的输入总有相同的输出,所以我们可以把纯函数的计算结果缓存起来
    • 可测试
      • 纯函数让测试更方便
    • 并行处理
      • 在多线程环境下并行操作共享的内存数据很可能会出现意外的情况
      • 纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数(Web Worker)
  • 纯函数的副作用

    • 副业用来源

      • 配置文件
      • 数据库
      • 获取用户的输入
      • ...
    • 副作用会让一个函数变得不纯,纯函数是根据相同的输入返回相同的输出,如果函数依赖于外部就无法保证输出相同,就会带来副作用

    • //示例
      let mini = 18
      function checkAge(age){
          return age > mini
      }
      
      //纯函数(存在硬编码)
      function checkAge(age){
          let mini = 18
          return age > mini
      }
      

柯里化

  • 概念

    • 当一个函数有多个参数的时候先传递一部分参数调用它,(这部分参数以后永远不变)
    • 然后返回一个新的函数接受剩余的参数,返回结果
  • lodash中的柯里化

    • _.curry(func)

      • 功能:创建一个函数,该函数接收一个或多个func的参数,如果func所需要的参数都被提供则执行func并返回执行的结果。否则继续返回该函数并等待接收剩余的参数。

      • 参数:需要坷里化的函数

      • 返回值:坷里化后的函数

      • //示例
        const _ = require("lodash")
        
        function add(a, b, c) {
            return a + b + c
        }
        
        const curried = _.curry(add)
        
        console.log(curried(1, 2, 3))
        console.log(curried(1, 2)(3))
        console.log(curried(1)(2)(3))
        
    • 柯里化案例

      • match: 可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。

      • filter:创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

      • curry:创建一个函数,该函数接收 func 的参数,要么调用func返回的结果,如果 func 所需参数已经提供,则直接返回 func 所执行的结果。或返回一个函数,接受余下的func 参数的函数,可以使用 func.length 强制需要累积的参数个数。

      • let _ = require("lodash")
        
        //纯函数
        const match = function (reg, str) {
            return str.match(reg)
        }
        
        console.log(match(/\s+/g, "Hello World"))  //输出:[ ' ' ]
        
        //柯里化
        const matchCurry = function (reg) {
            return function (str) {
                return str.match(reg)
            }
        }
        
        const haveSpace = matchCurry(/\s+/g)
        const haveNumber = matchCurry(/\d+/g)
        
        console.log(haveSpace("Hello World"))      //输出:[ ' ' ]
        console.log(haveNumber("Hello 2 World"))   //输出:[ '2' ]
        
        //lodash的柯里化函数
        const matchCurryByLodash = _.curry(function (reg, str) {
            return str.match(reg)
        })
        
        const haveSpaceByLodash = matchCurryByLodash(/\s+/g)
        const haveNumberByLodash = matchCurryByLodash(/\d+/g)
        
        console.log(haveSpaceByLodash("Hello World"))      //输出:[ ' ' ]
        console.log(haveNumberByLodash("Hello 2 World"))   //输出:[ '2' ]
        
        //filter
        const filter = _.curry(function (fn, array) {
            return array.filter(fn)
        })
        
        const findSpace = filter(haveSpace)
        const findSpaceByLodash = filter(haveSpaceByLodash)
        
        console.log(findSpace(['John Conner', 'Jogn_Donne']))          //输出:[ 'John Conner' ]
        console.log(findSpaceByLodash(['John Conner', 'Jogn_Donne']))  //输出:[ 'John Conner' ]
        
        //es6写法 一行代码搞定 “简单点 说话的方式简单点...”
        const matchByes6 = reg => (str) => str.match(reg)
        const filterLodashByEs6 = _.curry((fn) => (array) => array.filter(fn))
        
        const haveSpaceByEs6 = matchByes6(/\s+/g)
        const haveNumberByEs6 = matchByes6(/\d+/g)
        const findSpaceByEs6 = filterLodashByEs6(haveSpaceByEs6)
        
        console.log(haveSpaceByEs6("Hello World"))                   //输出:[ ' ' ]
        console.log(haveNumberByEs6("Hello 2 World"))                //输出:[ '2' ]
        console.log(findSpaceByEs6(['John Conner', 'Jogn_Donne']))   //输出:[ 'John Conner' ]
        
    • 自己实现坷里化

      • const curry = (func) => fn = (...args) => {
            if (args.length < func.length) {
                return function () {
                    return fn(...args.concat(Array.from(arguments)))
                }
            }
            return func(...args)
        }
        
        const curried2 = curry(add)
        
        console.log(curried2(1, 2, 3))
        console.log(curried2(1)(2, 3))
        console.log(curried2(1)(2)(3))
        
    • 总结

      • 柯里化可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定的新函数(闭包)

      • 让函数变的更灵活,让函数的粒度更小

      • 使用闭包,对函数参数的缓存

      • 可以把多元函数转换成一元的函数,可以组合使用函数产生功能更强大的函数

函数组合

  • 概念

    • 纯函数和坷里化很容易写出洋葱代码 h(g(f(x)))
      • 获取数组最后一个元素再转换成大写字母,_.toUpper(_.first(_.reverse(array)))
      • 函数组合可以让我们把粒度的函数重新组合生成一个新的函数
    • 如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个函数
      • 函数就是数据的管道,函数组合就是把这个管道连接起来,让数据穿过多个管道形成最终的结果
      • 函数组合默认是从右到左执行
      • 函数组合实现了细粒度函数的最大复用,因为函数可以多种组合从而实现不同的功能
    • 示例如下
    // 反转
    function reserve(arr){
        return arr.reverse()
    }
    // 获取第一个
    function first(arr){
       return arr[0]
    }
    //组合函数
    function compose(f,g){
      return function(value){
           return (f(g(value)))
       }
    }
    
    console.log(compose(first,reserve)([1,2,3,4,5]))
    
  • lodash中的函数组合

    // lodash 中的函数组合的方法 _.flowRight()
    const _ = require('lodash')
    
    const reverse = arr => arr.reverse()
    const first = arr => arr[0]
    const toUpper = s => s.toUpperCase()
    
    const f = _.flowRight(toUpper, first, reverse)
    console.log(f(['one', 'two', 'three']))
    
  • 模拟实现lodash中的flowRight函数

    const reverse = arr => arr.reverse()
    const first = arr => arr[0]
    const toLocaleUpperCase = str => str.toLocaleUpperCase()
    
    // const compose = function (...args) {
    //     return function (value) {
    //         return args.reverse().reduce(function (acc, fn) {
    //             return fn(acc)
    //         }, value)
    //     }
    // }
    
    const compose = (...args) => (value) => args.reverse().reduce((acc, fn) => fn(acc), value)
    
    let res = compose(toLocaleUpperCase, first, reverse)
    
    console.log(res(["one", "two", "three"]))
    
  • 结合律

    • 函数的组合要满足结合律
      • 我们既可以把g和h组合,还可以把f和g的组合,结果都是一样的
  • lodash中的fp模块

    • lodash的fp模块提供了使用的对函数式编程友好的方法

      • 提供了不可变auto-curried iteratee-firsr data-last的方法
      const fp = require('lodash/fp')
      
      const f = fp.flowRight(fp.join('-'), fp.map(fp.toLower), fp.split(' '))
      console.log(f('NEVER SAY DIE'))
      
    • lodash中的map函数与fp模块的map函数的区别在于接收的参数不一样

      • lodash中的函数接收的参数是3个,第1个是要处理的元素,第2个是索引,第3个是数组
      • fp模块中方法接收的参数只有一个,就是要处理的元素
  • PointFree

    • PointFree是一种编程风格,我们可以把数据处理的过程定义成与数据无关的合成运算,不需要用到代表数的那个参数,只要把简单的运算步骤合成到一起,在使用这种模式之前我们需要定义一些辅助的基本运算函数

      • 不需要指明处理的数据
      • 只需要合成运算过程
      • 需要定义一些辅助的基本运算函数
      /**
       * pointfree编程风格
       * 
       * 不需要指明数据来源
       * 只需要合成运算过程
       * 需要定义一些辅助的基本函数
       * 
       * world wild web ==> W. W. W
       */
      const fp = require("lodash/fp")
      
      const firstLetterToUpper = fp.flowRight(fp.join(". "), fp.map(fp.first), fp.split(" "), fp.toUpper)
      console.log(firstLetterToUpper("world wild web"))
      

Functor函子

  • 概念

    • 容器:包含值和值的变形关系(这个变形关系就是函数)
    • 函子:是一个特殊的容器,通过一个普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)
  • 总结

    • 函数式编程的运算不直接操作值,而是由函子完成
    • 函子就是一个实现了map契约的对象
    • 我们可以把函子想象成一个盒子,这个盒子里封装了一个值
    • 想要处理盒子中的值,我们需要给盒子的map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理
    • 最终map方法返回一个包含新值的盒子(函子)
    class Container {
      static of (value) {
        return new Container(value)
      }
    
      constructor (value) {
        this._value = value
      }
    
      map (fn) {
        return Container.of(fn(this._value))
      }
    }
    
    // let r = Container.of(5).map(x => x + 2).map(x => x * x)
    // console.log(r)
    
    // 演示 null undefined 的问题
    Container.of(null).map(x => x.toUpperCase())
    

    如果我们传递一个空值的情况,则会产生副作用,让我们这个函数变得不纯,函数式编程一个重要的概念就是相同的输入始终要得到相同的输出

MayBe函子

  • 我们在编程的过程中可能会遇到很多错误,我们需要对这些错误进行相应的处理

  • MayBe函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围)

    class MayBe {
        static of (value) {
          return new MayBe(value)
        }
      
        constructor (value) {
          this._value = value
        }
      
        map (fn) {
          return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
        }
      
        isNothing () {
          return this._value === null || this._value === undefined
        }
      }
        
      // let r = MayBe.of('Hello World')
      //           .map(x => x.toUpperCase())
      // console.log(r)
      
      // let r = MayBe.of(null)
      //           .map(x => x.toUpperCase())
      // console.log(r)
      
    
  • 注意,在 MayBe 函子中链式调用的时候,我们很难确认是哪一步产生的空值问题,如下例:

     let r = MayBe.of('hello world')
                .map(x => x.toUpperCase())
                .map(x => null)
                .map(x => x.split(' '))
      console.log(r)
    

Either函子

  • Either函子两者中的任何一个,类似与if...else...的处理

  • 异常会让函数变出不纯,Either函子可以用来做异常处理

    class Left {
        static of(value) {
            return new Left(value)
        }
    
        constructor(value) {
            this._value = value
        }
    
        map(fn) {
            return this
        }
    }
    
    class Right {
        static of(value) {
            return new Right(value)
        }
    
        constructor(value) {
            this._value = value
        }
    
        map(fn) {
            return Right.of(fn(this._value))
        }
    }
    
  • Either处理异常

    function parseJSON (str) {
      try {
        return Right.of(JSON.parse(str))
      } catch (e) {
        return Left.of({ error: e.message })
      }
    }
    
    // let r = parseJSON('{ name: xzz }')
    // console.log(r)
    
    let r = parseJSON('{ "name": "xzz" }')
              .map(x => x.name.toUpperCase())
    console.log(r)
    

IO函子

  • IO函子中的_value是一个函数

  • IO函子可以把不纯的动作存储到_value中,延迟执行这个不纯的操作(惰性执行)

  • 把不纯的操作交给调用者来处理

    // IO 函子
    const fp = require('lodash/fp')
    
    class IO {
      static of (value) {
        return new IO(function () {
          return value
        })
      }
    
      constructor (fn) {
        this._value = fn
      }
    
      map (fn) {
        return new IO(fp.flowRight(fn, this._value))
      }
    }
    
    // 调用
    let r = IO.of(process).map(p => p.execPath)
    // console.log(r)
    console.log(r._value())
    

Folktale

  • Task异步执行

    • 异步任务的实现过于复杂,我们使用folktale中的Task来演示
    • folktale一个标准的函数式编程库
      • 和lodash、ramda不同的是,他没有提供很多功能函数
      • 只提供了一些函数式处理的操作,例如:comppse,curry等,一些函子Task,Either,MayBe等
    // Task 处理异步任务
    const fs = require('fs')
    const { task } = require('folktale/concurrency/task')
    const { split, find } = require('lodash/fp')
    
    function readFile (filename) {
      return task(resolver => {
        fs.readFile(filename, 'utf-8', (err, data) => {
          if (err) resolver.reject(err)
    
          resolver.resolve(data)
        })
      })
    }
    
    readFile('package.json')
      .map(split('\n'))
      .map(find(x => x.includes('version')))
      .run()
      .listen({
        onRejected: err => {
          console.log(err)
        },
        onResolved: value => {
          console.log(value)
        }
      })
    

Pointed函子

  • Pointed函子是实现了of静态方法的函子
  • of方法是为了避免使用new来创建对象,更深层的函数是

Monad函子

  • Monad函子是可以变扁的Pointed函子,IO(IO(x))
  • 一个函子如果具有join和of两个方法并遵守一些定律就是一个Monad

JavaScript异步编程

执行模式

  • JavaScript将任务的执行模式分为两种:同步模式和异步模式
    • 同步模式指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
    • 异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行。
    • 只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。

回调函数

  • 回调函数是所有异步编程的根基
  • 回调函数可以理解为一件你想要做的申请
  • 调用者定义函数,执行者执行函数,调用者告诉执行者异步任务结束后应该做什么

Promise

  • 一种更优的异步编程统一方案,CommonJS社区提出了Promise的规范,目的为了给异步编程提供一种强大的更合理的解决方案,在ES2015中被标准化,成为语言规范
  • Promise实际上就是一个对象,用来表示一个异步任务最终结束过后究竟是成功还是失败,就像是对外界作出的一个承诺,最初的状态是Pending(等待),可能会成功Fulfilled(成功),也有可能失败Rejected(失败)。
  • 承诺结束过后都会有相对应的反映,如果成功了会调用onFulfilled,失败了会调用onRejected
  • 状态一旦发生变化过后就无法被改变

链式调用

  • Promise对象的then方法会返回一个全新的Promise对象

  • 后边的then方法就是为上一个then方法返回的Promise注册回调

  • 前面then方法中回调函数的返回值会作为后面then方法回调的参数

  • 如果回调中返回的是Promise,那后面then方法的回调就会等返回的Promise状态变为resolved才会执行

    ajax('/api/users.json')
      .then(function (value) {
        console.log(1111)
        return ajax('/api/urls.json')
      }) // => Promise
      .then(function (value) {
        console.log(2222)
        console.log(value)
        return ajax('/api/urls.json')
      }) // => Promise
      .then(function (value) {
        console.log(3333)
        return ajax('/api/urls.json')
      }) // => Promise
      .then(function (value) {
        console.log(4444)
        return 'foo'
      }) // => Promise
      .then(function (value) {
        console.log(5555)
        console.log(value)
      })
    

异常处理

  • Promise.prototype.catch 方法是 Promise.prototype.then(null, rejection) 的别名,用于指定发生错误时的回调函数。

  • 全局捕获异常(不推荐)

    // 全局捕获 Promise 异常,类似于 window.onerror
    window.addEventListener('unhandledrejection', event => {
      const { reason, promise } = event
    
      console.log(reason, promise)
      // reason => Promise 失败原因,一般是一个错误对象
      // promise => 出现异常的 Promise 对象
    
      event.preventDefault()
    }, false)
    

静态方法

  • Promise.resolve()
    • 如果传入的值为常量A,则返回一个值为A的Promise
    • 如果传入的值为promise对象,则返回其本身
    • 可以将对象转化为promise对象
  • Promise.reject()
  • Promise.all()
    • 解决异步并发问题,允许异步代码调用的顺序得到异步代码执行的结果
    • 等待所有的任务结束过后才会结束
  • Promise.race()
    • 可以把多个Promise对象组合成一个全新的Promise对象
    • 只会等待第一个结束的任务

执行时序

  • 回调队列中的任务称之为【宏任务】
  • 宏任务执行过程中可以临时加上一些额外的需求,这些额外的需求可以选择作为一个新的宏任务进到队列中排队
  • Promise的回调会作为微任务执行,在本轮结束的末尾去执行
  • 微任务的作用就是提高整体的响应能力
  • 目前绝大多数异步调用都是作为宏任务执行

Generator异步方案

  • Generator 函数是 ES2015提供的一种异步编程解决方案。

  • Generator 函数和普通函数不同的是,Generator是由function*定义,调用这个函数不会立即去执行这个函数,而是得到一个Generator对象,主动调用这个函数的next方法这个函数体才会开始执行。

  • 在函数内部可以使用yield返回一个值,在next方法返回对象中可以得到这个值

  • done表示这个Generator是否执行完毕

  • throw可以对Generator函数内部抛出一个异常

  • 执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

Async/Await

  • 语言层面的异步编程语法
  • async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await

手写Promise对象

  • Promise回顾

    • Promise就是一个类,在执行这个类的时候,需要传递一个执行器进去,执行器会立即执行
    • Promise中有三种状态,分别为成功(fulfilled)、失败(rejected)、等待(pending),一旦状态确定就不可更改
      • pending->fulfilled||rejected
    • resolve和reject函数是用来更改状态的
      • resolve:fulfilled
      • reject:rejected
    • then方法内部做的事情就是判断状态,如果状态是成功就调用成功回调函数,如果状态是失败就调用失败回调函数,then方法是被定义在原形对象中的
    • then成功回调有一个参数,表示成功之后的值,then失败回调有一个参数,表示失败后的原因
    • 同一个promise对象下面的then方法是可以被调用多次的
    • then方法是可以被链式调用的,后面then方法的回调函数拿到值的是上一个then方法的回调函数的返回值
  • 代码

    const PEDDING = 'pedding' //等待
    const FULFILLED = 'fulfilled' //成功
    const REJECTED = 'reject' //失败
    
    class MyPromise {
        /**
         * 通过构造函数来接收这个执行器
         * @param {*} executor 执行器
         */
        constructor(executor) {
            try {
                //这个执行器是立即执行的
                executor(this.resolve, this.reject)
            } catch (e) {
                this.reject(e)
            }
        }
    
        //记录Promise状态 默认:等待
        status = PEDDING
        //成功之后的值
        value = undefined
        //失败后的原因
        reason = undefined
        //成功回调
        successCallback = []
        //失败回调
        failCallback = []
    
        /**
         * 更改状态为成功,一旦状态确定将不可更改
         * 使用箭头函数定义是为了执行方法的时候让this指向MyPromise的实例对象
         * @param {*} value 成功后的值
         */
        resolve = value => {
            //如果状态不是等待,阻止程序向下执行
            if (this.status !== PEDDING) return
            //将状态更改为成功
            this.status = FULFILLED
            //保存成功之后的值
            this.value = value
            //判断成功回调是否存在,如果存在,调用存储的成功回调函数
            // this.successCallback && this.successCallback(this.value)
            // while (this.successCallback.length) this.successCallback.shift()(this.value)
            while (this.successCallback.length) this.successCallback.shift()()
        }
    
        /**
         * 更改状态为失败,一旦状态确定将不可更改
         * @param {*} reason 失败的原因
         */
        reject = reason => {
            //如果状态不是等待,阻止程序向下执行
            if (this.status !== PEDDING) return
            //将状态更改为失败
            this.status = REJECTED
            //保存失败后的原因
            this.reason = reason
            //判断失败回调是否存在,如果存在,调用存储的失败回调函数
            // this.failCallback && this.failCallback(this.reason)
            while (this.failCallback.length) this.failCallback.shift()()
        }
    
        /**
         * 判断状态,如果成功调用成功回调,如果失败调用失败回调
         * @param {*} successCallback 成功回调
         * @param {*} failCallback 失败回调
         */
        then(successCallback, failCallback) {
            //判断回调函数是否存在,如果存在就用这个回调函数,如果不存在就补充一个参数
            //实现调用then方法的时候,这个then方法不传递参数,这个then方法会依次调用,直到传递给有回调函数的then方法
            successCallback = successCallback ? successCallback : value => value
            failCallback = failCallback ? failCallback : reason => { throw reason }
    
            //实现链式调用,返回一个MyPromise对象
            let promose2 = new MyPromise((resolve, reject) => {
                // 同步执行,根据当前状态返回指定的回调函数
                if (this.status === FULFILLED) {
                    //将代码变成异步代码,同步代码执行完成之后才会执行,目的为了能够得到promose2对象
                    convert(() => resolvePromise(promose2, successCallback(this.value), resolve, reject), reject)
                } else if (this.status === REJECTED) {
                    convert(() => resolvePromise(promose2, failCallback(this.reason), resolve, reject), reject)
                } else {
                    //如果是等待状态(异步),将成功回调和失败回调存储起来
                    this.successCallback.push(() => convert(() => resolvePromise(promose2, successCallback(this.value), resolve, reject), reject))
                    this.failCallback.push(() => convert(() => resolvePromise(promose2, failCallback(this.reason), resolve, reject), reject))
                }
            })
    
            return promose2;
        }
    
        /**
         * 解决异步并发问题,允许异步代码调用的顺序得到异步代码执行的结果
         * @param {*} array 接收一个数组作为参数,这个数组当中的可以是任何值,包括普通值和promise对象,数组当中的顺序一定是执行结果的顺序
         */
        static all(array) {
            let result = []
    
            let index = 0
    
            return new MyPromise((resolve, reject) => {
                //添加执行结果数据到result
                const addData = (key, value) => {
                    result[key] = value
                    index++
                    //判断index的长度是否等于array的长度,因为for循环是一瞬间就执行完毕的,执行for循环的过程中存在异步操作,当长度一致时才去调用resolve方法
                    if (index === array.length) {
                        resolve(result)
                    }
                }
                for (let i = 0; i < array.length; i++) {
                    let current = array[i]
                    if (current instanceof MyPromise) {
                        //mypromise对象
                        //所有成功才返回成功,一次失败直接返回失败,这是all方法的一个特点
                        current.then(value => addData(i, value), reason => reject(reason))
                    } else {
                        //普通值
                        addData(i, array[i])
                    }
                }
            })
        }
    
        /**
         * 将传递的值转换成promise对象
         * @param {*} value 传递的值
         */
        static resolve(value) {
            if (value instanceof MyPromise) {
                return value
            } else {
                return new MyPromise(resolve => resolve(value))
            }
        }
    
        /**
         * 无论promise对象最终的状态是成功还是失败,该方法都会执行一次
         * 在finally后面可以链式调用得到这个方法的结果
         * @param {*} callback 回调函数
         */
        finally(callback) {
            return this.then(value => {
                return MyPromise.resolve(callback()).then(() => value)
            }, reason => {
                return MyPromise.resolve(callback()).then(() => { throw reason })
            })
        }
    
        /**
         * 用来处理当前这个promise对象最终的状态为失败的情况
         * @param {*} failCallback 失败的回调函数
         */
        catch(failCallback) {
            return this.then(undefined, failCallback)
        }
    }
    
    /**
     * 判断x的值是普通值还是promise对象
     * 如果是promise对象,查看promise对象返回的结果,再根据promise对象返回的结果 决定调用resolve还是reject
     * 如果是普通值,直接调用resolve
     * @param {*} promise 新的pormise对象
     * @param {*} x 当前操作的promise对象
     * @param {*} resolve 成功回调
     * @param {*} reject 失败回调
     */
    const resolvePromise = (promise, x, resolve, reject) => {
        //阻止promise对象循环调用
        if (promise === x) {
            return reject(new TypeError("Chaining cycle detected for promise #<Promise>"))
        }
        //判断X是否属于MyPromise对象
        if (x instanceof MyPromise) {
            // mypromise对象
            // x.then(value => { resolve(value) }, reason => { reject(reason) })
            x.then(resolve, reject)
        } else {
            //普通值
            resolve(x)
        }
    }
    
    /**
     * 将同步代码转换成异步代码
     * @param {*} resolve 需要转换的成功回调函数
     * @param {*} reject  需要转换的失败回调函数
     */
    const convert = (resolve, reject) => {
        setTimeout(() => {
            try {
                return resolve()
            } catch (e) {
                return reject(e)
            }
        }, 0);
    
    }
    
    module.exports = MyPromise