从0开始。从头开始手写Promise。

953 阅读12分钟

前提知识

高阶函数

高阶函数

满足以下条件之一:

  1. 函数的参数是一个 函数
  2. 函数的返回值是一个函数

闭包

定义: 有权访问另一个函数作用域中变量的函数(来源红宝书)

函数柯里化

柯里化(Currying),把接受多个参数的函数转换成接受一个单一参数的函数

举例

let add = function(x) {
        return function(y) {
            return x + y
        }
    }
    
add(3)(4)       // 7

实际开发如果需要用到 柯里化,推荐使用 lodash.curry

应用(类型判断)

typeof 无法判断 对象 类型

constructor 判断 是谁构造出来的

instanceof 判断 谁是谁的实例(即引用类型)

Object.prototype.toString.call() 完美判断

function isType(type) {
  return function (content) {
    return Object.prototype.toString.call(content) === `[object ${type}]`
  }
}
let isString = isType('String')
console.log(isString('132456'))
// 函数柯里化
console.log(isType('Number')(132456))

AOP 面向切片编程

也成为 装饰者模式

定义:指在不修改原有代码的情况下增加新功能

function sleep(who) {
  who?console.log(who + '睡觉'):console.log('睡觉')

}
Function.prototype.before = function (callback) {
  return (...args)=> {// args 传入的参数数组
    callback()
    args?this(...args):this() // 传入参数
  }
}
let Wash = sleep.before(function () {
  console.log('洗脸')
})
Wash()  // 洗脸 睡觉
Wash('我') // 洗脸 我睡觉

观察者模式

订阅发布

有挣议,有些人说这个不算是观察者模式。个人觉得算是属于

let e = {
  _obg:{}, // 事件 
  _callback:[], // 处理函数列表
  on(callback) { // 订阅
    this._callback.push(callback)
  },
  emit(key,value){  // 发布
    this._obg[key] = value
    this._callback.forEach(fn=>{
      fn(this._obg) // 参数传入
    })
  }
}
e.on(function (obj) {
  console.log('发布一个')
  console.log(obj)
})

setTimeout(function () {
  e.emit('name','琛')
},1000)

举一个简单的例子,即微博,你关注了A,A发动态就会通知你。你和A没有直接联系,通过 微博自己的调度来完成

观察者模式

与发布订阅区别

发布订阅模式是 两者之间没有直接关系,通过实践调度中心来完成。而观察者模式是相互依赖的,一个改变。另一个也发生改变

例子

// 设计模式  观察者模式

// 与发布订阅两者区别
//  发布订阅 是基于一个中间管理  on 和 emit 没有直接关系
//  观察者模式 是 Observer and Observed 有直接关系
// 一个依赖改变,另一个也改变 Vue
class Observer { // 观察者
  constructor(name) { // 传递参数
    this.name = name
  }
  update(baby){
    console.log(this.name+'知道'+baby.name+baby.state)
  }
}

class Observed{  // 被观察者
  constructor(name) {
    this.name = name
    this.state = '开心'
    this.Observer = []
  }
  addObserver(o){
    this.Observer.push(o)
  }
  setState(state){
    this.state = state
    this.Observer.forEach(o=>{
      o.update(this)
    })
  }
}

let baby = new Observed('宝宝')
let dad = new Observer('爸爸')
let mom = new Observer('妈妈')
// 添加观察者
baby.addObserver(dad)
baby.addObserver(mom)
// 设置状态
baby.setState('不开心')
baby.setState('开心')

进入正题

基本的promise使用

promise

用来解决异步

Promise 是一个天生的类 需要传入一个函数 默认会立即执行

有三种状态 ,成功(resolve) ,失败(reject), 等待

基本用法

let a = new Promise((resolve, reject) => {
    // 这两个方法可以更改promise 状态
    // resolve()如果是这样 下面的输出为 undefined
  resolve('成功')
    // reject('失败')
})
a.then((data)=>{
  console.log(data) // 成功
},(err)=>{
  console.log(err) // 失败
})

resolve,reject 这两个方法可以改变状态。如果是 resolve,则走then的第一个函数,rejectthen的第二个函数。并且都将参数传入。

**注意:**走成功了就不可以走失败,反之亦然

从0开始,手写Promise

明白了基本用法,我们开始模仿一下Promise。这里使用ES6语法来模仿。

这是Promise的规范

同步实现

首先,需要就收一个executor,是一个函数。立即执行,有三那种状态,里面还有resolve,reject两个参数,这两个参数是函数,需要接收两个参数。同时promise还有then方法

思路分析

// promise 同步
// 三种状态的定义
const Pending = 'PENDING'
const Success = 'SUCCESS'
const Error = 'Error'

// promise 的实现
class WritePromise {
  constructor(fn) { // 初始化
      
    this.state = Pending // 状态初始化
    this.value = undefined // 成功信息
    this.err = undefined // 错误信息
      
    let resolve = (value)=>{ 
      if (this.state === Pending){
        this.value = value
        this.state = Success
      }
    }
    let reject = (err)=>{
      if (this.state === Pending){
        this.value = err
        this.state = Error
      }
    }
    //  可能会出错
    try {
      fn(resolve,reject) // 立即执行
    }catch (e) {
      console.log(e) // 如果内部出错 直接交给reject 方法向下传递
      reject(e)
    }
  }
    
  then(onfulfilled,onrejected){
    switch (this.state) {
      case Success:
        onfulfilled(this.value)
        break
      case Error:
        onrejected(this.err)
        break
    }
  }
}
// export default WritePromise  浏览器端
module.exports = WritePromise

在new的过程中,执行constructor,传入的 resolveorreject,进行赋值和状态改变。然后then方法更具 state的状态进行不同的操作。onfulfilled and onrejected是传入的操作函数

为什么要加try catch

在使用过程中,不仅仅只有reject可以接收错误,也可以手动抛出错误。这样就reject捕获不到错误。 所以要加上 try catch 。 保证可以正常运行

测试

let WritePromise = require('./WritePromise')
let promise = new WritePromise((resolve, reject) => {
  // 1.
    resolve('成功') 
    // 2.
    // reject('失败')
})

promise.then((data) => { // onfulfilled 成功
  console.log(data)
}, (err) => { // onrejected 失败
  console.log(err)
})

// 输出  1.  成功   2. 失败

异步实现

大家会发现。如果在resolveorreject,执行异步代码(例如定时器)。会发现没有结果。这是因为我们刚才写的都是同步代码。现在要改一下,改成异步的

这时候就用到我们前面的知识了,发布订阅模式

思路

​ 首先,我们应该知道在constructor中传入的fn,如果加上定时器的话,它的状态state不会发生任何改变。也就是一直处于等待状态, 所以并不会执行then里面的函数。所以我们应该考虑一下当他处于等待的时候。是不是应该吧传入的函数存储起来,等到上面执行resolveorreject的时候,再把这个函数执行。

实现

// promise 异步
const Pending = 'PENDING'
const Success = 'SUCCESS'
const Error = 'Error'
class WritePromiseAsync {
  constructor(fn) {
    this.state = Pending
    this.value = undefined
    this.err = undefined
      //  回调函数的存储
    this.SuccessCal = []
    this.ErrorCal = []

    let resolve = (value)=>{
      if (this.state === Pending){
        this.value = value
        this.state = Success
          // 对回调函数进行变量  然后执行
        this.SuccessCal.forEach((fn)=>fn())
      }
    }
    let reject = (err)=>{
      if (this.state === Pending){
        this.value = err
        this.state = Error
        this.ErrorCal.forEach((fn)=>fn())
      }
    }
    //  可能会出错
    try {
      fn(resolve,reject) // 立即执行
    }catch (e) {
      console.log(e) // 如果内部出错 直接交给reject 方法向下传递
      reject(e)
    }
  }
  then(onfulfilled,onrejected){
    switch (this.state) {
      case Success:
        onfulfilled(this.value)
        break
      case Error:
        onrejected(this.err)
        break
      case Pending:
        this.SuccessCal.push(()=>{ 
            // 为什么要这样写 因为这样可以做一些逻辑 AOP
          //  这里面可以做一些逻辑
          onfulfilled(this.value)
        })
        this.ErrorCal.push(()=>{
          onrejected(this.err)
        })
        break
    }
  }
}
module.exports = WritePromiseAsync

在顺一遍。 创建对象之后,调用then方法, 代码开始执行,执行到then的时候,发现没有对应的状态改变,就先把它存储起来。等到定时器结束之后,在把所有的函数都执行一次

链式调用

  • 每次调用返回的都是一个新的Promise实例(这就是为什么可以一直then)
  • 链式调用的参数通过返回值传递
// 第二点的 代码解释

let b = new Promise((resolve, reject) => {
  resolve('data')
}).then((data)=>{
  data = data + '132456'  
    // then可以返回一个值,如果是普通值。就会走到下一个then 的成功中
  return data
}).then((data)=>{
  console.log(data) // 输出 data132456
})

如果返回的不是普通值,是promise,则会使用这个promise的结果

let b = new Promise((resolve, reject) => {
  resolve('data')
}).then((data)=>{
  data = data + '132456'  
  return data
}).then(()=>{
  return new Promise((resolve, reject) => {
    resolve('我是promise 的返回') 
      // 如果返回的是一个promise,那么会采用这个promise的结果
  })
}).then((data)=>{
  console.log(data) // 输出 我是promise 的返回
})

catch

用来捕获 最近的且没有捕获的错误

let b = new Promise((resolve, reject) => {
  reject('data')
}).then().catch(err=>{ // 捕获错误  捕获最近的没有捕获的错误
      console.log(err+'catch') // datacatch
      // 注意 返回的也是undefined
    })

注意点

上述走的是成功,失败也一样。但会有一个小坑。

let b = new Promise((resolve, reject) => {
  resolve('data')
}).then(()=>{},err=>{
      console.log(err) 
    // 在失败函数中如果返回的是一个普通值,也会走下一次then的成功中
      // return undefined  相当于返回了一个这个
    }).then((data)=>{
      console.log(data+'success') // 这个会走 成功的值 输出 underfinedsuccess
    },(err)=>{
      console.log(err+'err')
    })

特别注意,这里会经常有遗漏。

链式调用的手写实现

接着上次的WritePromiseAsync

实现多次then传递 思路

原版做法中,当连续调用then方法的时候,会把上一次的结果传递给下一个then

上面说过每次调用then方法会返回一个promise实例。所以,我们需要在调用then方法的时候返回一个promise的实例,并且接收到then方法的结果。在传递给这个promise

// 多余的我就不写了,主要写差异化 的 then方法
 then(onfulfilled, onrejected) {
    let promise2 = new ChainPromise((resolve, reject) => {
      let x
      switch (this.state) {
        case Success:
          x = onfulfilled(this.value)
          resolve(x)
          break
        case Error:
          x = onrejected(this.value)
          reject(x)
          break
        case Pending:
          this.SuccessCal.push(() => { 
            try {
              let x = onfulfilled(this.value)
              resolve(x)
            } catch (e) {
              reject(e)
            }
          })
          this.ErrorCal.push(() => {
            try {
              let x = onrejected(this.err)
              reject(x)
            } catch (e) {
              reject(e)
            }
          })
          break
      }
    })
    return promise2
  }

注意,调用的时候要把 then的两个函数都要写上,否则会报错(还没有处理)

这样过后 就可以实现 多次then方法传递结果了

实现 返回promise 思路

说一下上面得哪个x,我们是直接把它返回给对应得处理方法,如果x是一个promise呢? 按照原版得来说。我们应该把这个promise的结果作为返回值来继续传递。所以我们应该对这个x进行处理

创建一个方法solveX,来处理x

function solveX(promise2, x, resolve, reject) {
  if (promise2 === x){
    return reject(new TypeError('引用本身'))
  }
  if ((typeof x === 'object' && x != null)|| typeof x === 'function'){
    // 处理promise
    try {
      let then = x.then
      if (typeof then === 'function'){ // 只能认定他是promise了
        then.call(x,(data)=>{
          console.log(data)
          resolve(data)
        },(err)=>{
          reject(err)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      reject(e) // 取值失败 走err
    }
  }else {
    // 是一个普通值
    resolve(x)
  }
}

为什么要把promise2传进来呢? 因为如果 x就是promise2呢?则会是一个死循环。

x进行判断,如果是普通值,直接返回就可以了。如果不是,我们取then方法(注意是方法,不是结果). 如果有这个方法,我们就认定他是一个promise(可能有人会说如果then是一个空方法呢?,那也只能认定了,我们最多只能做到这种程度的判断了。)

注意then的this 指向问题

关于promise2传入问题

try {
    x = onfulfilled(this.value)
    resolvePromise(promise2, x, resolve, reject) 
    // 需要用x 来比较promise2的值
              // resolve()
} catch (e) { // 一旦出错,走下一个promise 的错误处理方法
    reject(e)
}

如果直接传入promise2的话,因为是同步的过程,在创建的时候promise2还没有生成,所以会报错。这时候我们可以加一个定时器,把它变成异步。这就解决了这个问题

then(onfulfilled, onrejected) {
    let promise2 = new ChainPromise((resolve, reject) => {
      let x
      switch (this.state) {
        case Success:
          setTimeout(() => { // 如果不加定时器,promise2获取不到
            try {
              x = onfulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject) // 需要用x 来比较promise2的值
              // resolve()
            } catch (e) { // 一旦出错,走下一个promise 的错误处理方法
              reject(e)
            }
          }, 0)
          //  实现之后要判断 X  如果x是一个普通值,就正常返回。如果是一个promise 则把promise的执行结果作为参数传递给 相应的处理函数
          break
        case Error:
          setTimeout(() => {
            try {
              x = onrejected(this.err)
              // reject(x)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
          break
        case Pending:
          this.SuccessCal.push(() => { 
            try {
              let x = onfulfilled(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
          this.ErrorCal.push(() => {
            try {
              let x = onrejected(this.err)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
          break
      }
    })
    return promise2
  }

注意,即使写的是0,也不会立即执行。

解决then里面继续返回promise

上面我们写了一个方法来处理promise,只需要进行一个递归就可以解决

/**
 *
 * @param promise2
 * @param x
 * @param resolve
 * @param reject
 * @returns {*}
 */
function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('引用本身'))
  }
  if ((typeof x === 'object' && x != null) || typeof x === 'function') {
    // 处理promise
    try {
      let then = x.then
      if (typeof then === 'function') { 
        then.call(x, (data) => {
          // resolve(data)  将data重新放入这个函数。直到是一个普通值再进行返回
          resolvePromise(promise2, data, resolve, reject)
        }, (err) => {
          // reject(err)
          resolvePromise(promise2, err, resolve, reject)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      reject(e) // 取值失败 走err
    }
  } else {
    // 是一个普通值
    resolve(x)
  }
}

解决then必须传值

then(onfulfilled, onrejected) {
    onfulfilled = typeof onfulfilled === 'function'?onfulfilled:v=>v
    onrejected = typeof onrejected === 'function'?onrejected:err=>{throw err}
    ........
}

将两个函数进行判断。如果不是函数,默认赋一个函数

防止多次调用

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>--'))
  }
  let called  // 添加一个变量进行控制
  if ((typeof x === 'object' && x != null) || typeof x === 'function') {
    try {
      let then = x.then
      if (typeof then === 'function') {
        then.call(x, y => {
          if (called) return
          called = true
          resolvePromise(promise2, y, resolve, reject)
        }, r => {
          if (called) return // 如果发现被调用过 直接return
          called = true
          reject(r)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}

总结代码

// promise 链式调用的实现
const PENDING  = 'PENDING'
const RESOLVED  = 'RESOLVED '
const REJECTED  = 'REJECTED'

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>--'))
  }
  let called
  if ((typeof x === 'object' && x != null) || typeof x === 'function') {
    try {
      let then = x.then
      if (typeof then === 'function') {
        then.call(x, y => {
          if (called) return
          called = true
          resolvePromise(promise2, y, resolve, reject)
        }, r => {
          if (called) return
          called = true
          reject(r)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}

class Promise {
  constructor(executor) {
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    this.SuccessCal = []
    this.ErrorCal = []

    let resolve = value => {
      if (this.status === PENDING) {
        this.value = value
        this.status = RESOLVED
        this.SuccessCal.forEach(fn => fn())
      }
    }
    let reject = reason => {
      if (this.status === PENDING) {
        this.reason = reason
        this.status = REJECTED
        this.ErrorCal.forEach(fn => fn())
      }
    }
    try {
      executor(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  then(onRESOLVED, onrejected) {
    onRESOLVED = typeof onRESOLVED === 'function' ? onRESOLVED : v => v
    onrejected = typeof onrejected === 'function' ? onrejected : err => {
      throw err
    }
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === RESOLVED) {
        setTimeout(() => { // 如果不加定时器,promise2获取不到
          try {
            let x = onRESOLVED(this.value)
            resolvePromise(promise2, x, resolve, reject) // 需要用x 来比较promise2的值
          } catch (e) { // 一旦出错,走下一个promise 的错误处理方法
            reject(e)
          }
        }, 0)
        //  实现之后要判断 X  如果x是一个普通值,就正常返回。如果是一个promise 则把promise的执行结果作为参数传递给 相应的处理函数
      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onrejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      }
      if (this.status === PENDING) {
        this.SuccessCal.push(() => { // 为什么要这样写 因为这样可以做一些逻辑
          //  这里面可以做一些逻辑
          setTimeout(() => {
            try {
              let x = onRESOLVED(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
        this.ErrorCal.push(() => {
          setTimeout(() => {
            try {
              let x = onrejected(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
      }
    })
    return promise2
  }
}



// 测试 需要测试再添加
Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}
module.exports = Promise

关于符合性测试

这个是测试工具的github

安装之后, 执行

npx promises-aplus-tests promise.js

为什么是npx? 我没有全局安装

全部通过

测试结果.png