xxleyi / learning_list

聚集自己的学习笔记
10 stars 3 forks source link

Promise 拆解实现 #222

Open xxleyi opened 4 years ago

xxleyi commented 4 years ago

参考:

Promise 拆解实现:

// 1. 最核心的 resolve 和 then

// 同步的 resolve 和 then
function Promise(executor) {
  let value = null

  const resolve = (res) => {
    value = res
  }

  this.then = (onFulfilled) => {
    onFulfilled(value)
  }

  executor(resolve)
}

new Promise(resolve => resolve('sync')).then(res => console.log(res))

// 同时支持同步和异步的 resolve 和 then
// resolve 控制 onFulfilled 的执行时机:next tick
// then 提供 onFulfilled,但不会执行
// 故而需要一个中间变量:deferAction 来保存 onFulfilled
function Promise(executor) {
  let value = null
  let deferAction = null

  const resolve = (res) => {
    value = res
    setTimeout(() => deferAction(value))
  }

  this.then = (onFulfilled) => {
    deferAction = onFulfilled
  }

  executor(resolve)
}

new Promise(resolve => resolve('sync')).then(res => console.log(res))
new Promise(resolve => setTimeout(() => resolve('async'), 0)).then(res => console.log(res))

以上两块实现,分别完成了核心功能和基于核心功能的必要扩展两部分的目标。

但这其实有个致命问题,就是异步控制完全放在 resolve 中,属于强制延迟一轮,如果 resolve 本身已经被延迟执行过,这个延迟其实没必要,也不太合理。这促使我们将延迟机制放在 then 内部,这样的话,onFulfilled 的执行时机就有了两个选择:

  1. then 异步开始执行的时候,value 已经拿到了值
  2. then 异步开始执行的时候,value 还没拿到值

这两种选择是竞争关系,只能选其一,因此需要为 value 的变化引入状态:PENDING 和 FULLFILLED。不同的状态下分别做不同的事情。

function Promise(executor) {
  const PENDING = 0
  const FULFILLED = 1

  let value = null
  let status = PENDING
  let deferAction = null

  // resolve 只要执行完毕,状态肯定变为 FULFILLED,deferAction 肯定为空
  const resolve = (res) => {
    value = res
    status = FULFILLED
    if (typeof deferAction === 'function') handle(deferAction)
    deferAction = null
  }

  // onFulfilled 延迟一轮处理,处理的时候,如果依旧在 PENDING 状态,则将 onFulfilled 赋值给 deferAction
  // 如果已经在 FULFILLED 状态,则可以直接运行 onFulfilled
  const handle = (onFulfilled) => {
    if (status === PENDING) {
      deferAction = onFulfilled
    } else if (status === FULFILLED) {
      onFulfilled(value)
    }
  }

  this.defer = (onFulfilled) => {
    setTimeout(() => handle(onFulfilled))
  }

  this.then = (onFulfilled) => {
    this.defer(onFulfilled)
  }

  executor(resolve)
}

new Promise(resolve => resolve('sync')).then(res => console.log(res))
new Promise(resolve => setTimeout(() => resolve('async'), 0)).then(res => console.log(res))

let p = new Promise(resolve => resolve('second sync'))
p.then(res => console.log(res))
console.log('before second sync')

怎么样,是不是有点意思?


下面考虑继续完善,添加 reject 以及由此带来的状态控制机,以及支持多个 action

// 2. 添加 reject 和由此带来的状态机控制

function Promise(executor) {
  const PENDING = 0
  const FULFILLED = 1
  const REJECTED = 2

  let value = null
  let status = PENDING
  let deferActionOnFulfilled = null
  let deferActionOnRejected = null

  const fulfill = (res) => {
    value = res
    status = FULFILLED
    if (typeof deferActionOnFulfilled === 'function') handle(deferActionOnFulfilled)
    deferActionOnFulfilled = null
  }

  const reject = (reason) => {
    value = reason
    status = REJECTED
    if (typeof deferActionOnRejected === 'function') handle(null, deferActionOnRejected)
    deferActionOnRejected = null
  }

  const doResolve = (executor) => {
    let done = false
    try {
      executor(
        (res) => {
          if (done) return
          done = true
          fulfill(res)
        },
        (reason) => {
          if (done) return
          done = true
          reject(reason)
        }
      )
    } catch (ex) {
      if (done) return
      done = true
      reject(ex)
    }
  }

  const handle = (onFulfilled, onRejected) => {
    if (status === PENDING) {
      deferActionOnFulfilled = onFulfilled
      deferActionOnRejected = onRejected
    } else if (status === FULFILLED) {
      onFulfilled(value)
    } else if (status === REJECTED) {
      onRejected(value)
    }
  }

  this.defer = (onFulfilled, onRejected) => {
    setTimeout(() => handle(onFulfilled, onRejected))
  }

  this.then = (onFulfilled, onRejected) => {
    this.defer(onFulfilled, onRejected)
  }

  doResolve(executor)
}

new Promise(resolve => resolve('sync')).then(res => console.log(res))
new Promise(resolve => setTimeout(() => resolve('async'), 0)).then(res => console.log(res))
new Promise(resolve => {
  throw Error('oh, my god.')
  resolve('async')
}).then(null, ex => console.log("" + ex))

let p = new Promise(resolve => resolve('second sync'))
p.then(res => console.log(res))
console.log('before second sync')

let p2 = new Promise((resolve, reject) => reject('third error sync'))
p2.then(null, res => console.log(res))
console.log('before third error sync')

new Promise(resolve => {
  resolve('async')
  throw Error('oh, my god.')
  resolve('async')
}).then(res => console.log(res), ex => console.log("should not be here: " + ex))

继续完善,支持 多个 action 和 chainable then

// 继续完成,支持 chainable then
// 也就是说,then 的返回值,理应是一个新的 promise 对象
// 并且给新的 promise 对象写好 onFulfilled onRejected 函数
function Promise(executor) {
  const PENDING = 0
  const FULFILLED = 1
  const REJECTED = 2

  let value = null
  let status = PENDING
  let deferActionsOnFulfilled = []
  let deferActionsOnRejected = []

  const fulfill = (res) => {
    value = res
    status = FULFILLED
    deferActionsOnFulfilled.forEach(action => {
      handle(action)
    })
    deferActionsOnFulfilled = null
  }

  const reject = (reason) => {
    value = reason
    status = REJECTED
    deferActionsOnRejected.forEach(action => {
      handle(null, action)
    })
    deferActionsOnRejected = null
  }

  const resolve = (res) => {
    try {
      if (res instanceof Promise) {
        doResolve(res.then.bind(res), resolve, reject)
      } else {
        fulfill(res)
      }
    } catch (ex) {
      reject(ex)
    }
  }

  const handle = (onFulfilled, onRejected) => {
    if (status === PENDING) {
      deferActionsOnFulfilled.push(onFulfilled)
      deferActionsOnRejected.push(onRejected)
    } else if (status === FULFILLED) {
      onFulfilled(value)
    } else if (status === REJECTED) {
      onRejected(value)
    }
  }

  const doResolve = (executor, resolve, reject) => {
    let done = false
    try {
      executor(
        (res) => {
          if (done) return
          done = true
          resolve(res)
        },
        (reason) => {
          if (done) return
          done = true
          reject(reason)
        }
      )
    } catch (ex) {
      if (done) return
      done = true
      reject(ex)
    }
  }

  this.defer = (onFulfilled, onRejected) => {
    setTimeout(() => handle(onFulfilled, onRejected))
  }

  this.then = (onFulfilled, onRejected) => {
    return new Promise((resolve, reject) => {
      this.defer(res => {
        if (typeof onFulfilled === 'function') {
          try {
            resolve(onFulfilled(res))
          } catch (ex) {
            reject(ex)
          }
        } else {
          resolve(res)
        }
      }, reason => {
          if (typeof onRejected === 'function') {
            try {
              resolve(onRejected(reason))
            } catch (ex) {
              reject(ex)
            }
          } else {
            reject(reason)
          }
      })
    })
  }

  doResolve(executor, resolve, reject)
}

new Promise(resolve => resolve('sync')).then(res => console.log(res))
new Promise(resolve => setTimeout(() => resolve('async'), 0)).then(res => console.log(res))
new Promise(resolve => {
  throw Error('oh, my god.')
  resolve('async')
}).then(null, ex => console.log("" + ex))

var p = new Promise(resolve => setTimeout(() => resolve('async'), 0))
p.then(res => console.log(res, 'then1'))
p.then(res => console.log(res, 'then2'))

var p = new Promise(resolve => {
  throw Error('oh, my god.')
  resolve('async')
})
p.then(res => console.log(res, 'then1'), ex => console.log("error1: " + ex))
p.then(res => console.log(res, 'then2'), ex => console.log("error2: " + ex))

var p = new Promise(resolve => setTimeout(() => resolve('async'), 0))

p.then(res => {
  return "chainable then " + res
}).then(res => console.log(res))

p.then(res => {
  return new Promise(r => setTimeout(() => r("chainable then with promise " + res)))
}).then(res => console.log(res)).then(res => console.log("should be be undefined", res))

到现在为止,就只剩下 catch 部分没有实现了,而 catch 的实现理应很简单。但到此时,需要再进行一点处理:

function Promise(executor) {
  const PENDING = 0
  const FULFILLED = 1
  const REJECTED = 2

  let value = null
  let status = PENDING
  let deferHandlers = []

  const fulfill = (res) => {
    value = res
    status = FULFILLED
    deferHandlers.forEach(handle)
    deferHandlers = []
  }

  const resolve = (res) => {
    const isPromise = res instanceof Promise
    try {
      if (isPromise) doResolve(res.then.bind(res), resolve, reject)
      else fulfill(res)
    } catch (ex) {
      reject(ex)
    }
  }

  const reject = (reason) => {
    value = reason
    status = REJECTED
    deferHandlers.forEach(handle)
    deferHandlers = []
  }

  const handle = (handler) => {
    if (status === PENDING) {
      deferHandlers.push(handler)
    } else {
      if (status === FULFILLED) handler.onFulfilled(value)
      if (status === REJECTED) handler.onRejected(value)
    }
  }

  this.defer = (onFulfilled, onRejected) => {
    setTimeout(() => handle({ onFulfilled, onRejected }))
  }

  const doResolve = (executor, resolve, reject) => {
    let done = false
    try {
      executor(
        (res) => {
          if (done) return
          done = true
          resolve(res)
        },
        (reason) => {
          if (done) return
          done = true
          reject(reason)
        }
      )
    } catch (ex) {
      if (done) return
      done = true
      reject(ex)
    }
  }

  this.then = (onFulfilled, onRejected) => {
    const self = this
    return new Promise((resolve, reject) => {
      self.defer(
        (res) => {
          if (typeof onFulfilled === 'function') {
            try {
              resolve(onFulfilled(res))
            } catch (ex) {
              reject(ex)
            }
          } else {
            resolve(res)
          }
        },
        (reason) => {
          if (typeof onRejected === 'function') {
            try {
              resolve(onRejected(reason))
            } catch (ex) {
              reject(ex)
            }
          } else {
            reject(reason)
          }
        }
      )
    })
  }

  this.catch = (onRejected) => {
    return this.then(null, onRejected)
  }

  doResolve(executor, resolve, reject)
}

new Promise(resolve => resolve('sync')).then(res => console.log(res))
new Promise(resolve => setTimeout(() => resolve('async'), 0)).then(res => console.log(res))
new Promise(resolve => {
  throw Error('oh, my god.')
  resolve('async')
}).then(null, ex => console.log("" + ex))

var p = new Promise(resolve => setTimeout(() => resolve('async'), 0))
p.then(res => console.log(res, 'then1'))
p.then(res => console.log(res, 'then2'))

var p = new Promise(resolve => {
  throw Error('oh, my god.')
  resolve('async')
})
p.then(res => console.log(res, 'then1'), ex => console.log("error1: " + ex))
p.then(res => console.log(res, 'then2'), ex => console.log("error2: " + ex))

var p = new Promise(resolve => setTimeout(() => resolve('async'), 0))

p.then(res => {
  return "chainable then " + res
}).then(res => console.log(res))

p.then(res => {
  return new Promise(r => setTimeout(() => r("chainable then with promise " + res)))
}).then(res => console.log(res)).then(res => console.log("shouldbe be undefined", res))

var errorPromise = p.then(null).then(
  a => {
  console.log(a, 'after null')
  a()
})

errorPromise.then().then().catch(e => console.log('' + e, 'catch after then'))
errorPromise.catch(e => console.log('' + e, 'multi catch'))
xxleyi commented 4 years ago

重整思路之后的又一微调版本,学到很多。现在回顾 Promise 的实现,其中涉及的点还是相当多的,列举如下:

function Promise(executor) {
  let value = null
  let status = PENDING
  let deferHandlers = []

  const fulfill = (res) => {
    value = res
    status = FULFILLED
    deferHandlers.forEach(handle)
    deferHandlers = null
  }

  const reject = (reason) => {
    value = reason
    status = REJECTED
    deferHandlers.forEach(handle)
    deferHandlers = null
  }

  const resolve = (res) => {
    const then = getThen(res)
    try {
      if (then) {
        doResolve(then.bind(res), resolve, reject)
      } else {
        fulfill(res)
      }
    } catch (ex) {
      reject(ex)
    }
  }

  const handle = (handler) => {
    if (status === PENDING) deferHandlers.push(handler)
    else if (status === FULFILLED) handler.onFulfilled(value)
    else if (status === REJECTED) handler.onRejected(value)
  }

  this.defer = (onFulfilled, onRejected) => {
    setTimeout(() => handle({ onFulfilled, onRejected }))
  }

  this.then = (onFulfilled, onRejected) => {
    return new Promise(
      (resolve, reject) => {
        this.defer(
          res => {
            if (typeof onFulfilled === 'function') {
              try {
                resolve(onFulfilled(res))
              } catch (ex) {
                reject(ex)
              }
            } else {
              resolve(res)
            }
          },
          reason => {
            if (typeof onRejected === 'function') {
              try {
                resolve(onRejected(reason))
              } catch (ex) {
                reject(ex)
              }
            } else {
              reject(reason)
            }
          })
      })
  }

  this.catch = (onRejected) => {
    return this.then(null, onRejected)
  }

  doResolve(executor, resolve, reject)
}

const PENDING = 0
const FULFILLED = 1
const REJECTED = 2

const doResolve = (executor, resolve, reject) => {
  let done = false
  try {
    executor(
      (res) => {
        if (done) return
        done = true
        resolve(res)
      },
      (reason) => {
        if (done) return
        done = true
        reject(reason)
      }
    )
  } catch (ex) {
    if (done) return
    done = true
    reject(ex)
  }
}

const getThen = (res) => {
  try {
    return res.then
  } catch { }
}

new Promise(resolve => resolve('sync')).then(res => (console.log(res), 33)).then(res => console.log(res))
new Promise(resolve => setTimeout(() => resolve('async'), 0)).then(res => console.log(res))

var p = new Promise(resolve => setTimeout(() => resolve('async'), 0))

p.then(res => {
  return "chainable then " + res
}).then(res => console.log(res))

p.then(res => {
  return new Promise(r => setTimeout(() => r("chainable then with promise " + res)))
}).then(res => console.log(res)).then(res => console.log("should be be undefined", res))

let genObjWithThen = () => {
  const val = 'objWithThen'
  const p = {}
  p.then = (onFulfilled) => {
    console.log('then', val)
    onFulfilled(val)
  }
  return p
}

p.then(() => genObjWithThen()).then((res) => console.log(res, 'getObjWithThen'))

var errorPromise = p.then(null).then(
  a => {
    console.log(a, 'after null')
    a()
  })

errorPromise.then().then().catch(e => console.log('' + e, 'catch after then'))
new Promise(resolve => setTimeout(() => resolve('async'), 0)).then(res => errorPromise).catch(e => console.log('' + e, 'catch after error promise'))

以上这个实现版本的功能实现相对官方实现算是比较齐全,其中比较关键的几个处理点如下:

  1. defer 方式
    • 因为要同时处理异步和同步情况,并且全部处理成异步,只有两个选择:
    • 要么在 resolve 中 defer
    • 要么在 then 中 defer
    • 考虑 then 必须是异步的,在 then 中异步会更合理一些,在 resolve 中处理会出现不必要的延迟
  2. chainable 方式
    • 将 resolve 和 onFulfilled 同时 defer
    • 封装新的 onFulfilled
  3. resolve 需要应对的问题
    • thenable value
    • 出错时降级到 reject
  4. 状态机控制
    • 状态转变有且只有一次
  5. 综合 resolve 和状态机控制,选择在不安全的 resolve 基础上封装一层状态控制是比较好的方式