2
0
0

手写Promise,我终于搞懂了异步编程

文章摘要
|

学前端如果只能选一个东西彻底吃透,我会选Promise。

不是因为面试常考,而是理解了Promise,就理解了JavaScript最核心的异步模型。从回调地狱到async/await,整个演进脉络全在Promise这一条线上。

花了一个周末自己实现了一个符合Promise/A+规范的简易版本,分享核心代码和思路。

第一步:构造函数和状态管理

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  constructor(executor) {
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []
    
    const resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED
        this.value = value
        this.onFulfilledCallbacks.forEach(fn => fn())
      }
    }
    
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED
        this.reason = reason
        this.onRejectedCallbacks.forEach(fn => fn())
      }
    }
    
    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }
}

关键点:状态一旦改变就不能再变,所以resolve和reject里都要判断status是否为PENDING。

第二步:then方法的实现

then是Promise最核心的方法,也是最复杂的部分:

then(onFulfilled, onRejected) {
  // 参数可选且必须为函数
  onFulfilled = typeof onFulfilled === 'function' 
    ? onFulfilled 
    : value => value
  onRejected = typeof onRejected === 'function' 
    ? onRejected 
    : reason => { throw reason }
  
  const promise2 = new MyPromise((resolve, reject) => {
    if (this.status === FULFILLED) {
      setTimeout(() => {
        try {
          const x = onFulfilled(this.value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (error) {
          reject(error)
        }
      }, 0)
    }
    
    if (this.status === REJECTED) {
      setTimeout(() => {
        try {
          const x = onRejected(this.reason)
          resolvePromise(promise2, x, resolve, reject)
        } catch (error) {
          reject(error)
        }
      }, 0)
    }
    
    if (this.status === PENDING) {
      this.onFulfilledCallbacks.push(() => {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      })
      this.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      })
    }
  })
  
  return promise2
}

这里有几个关键设计:

  1. setTimeout包裹——保证then是异步执行的,符合微任务规范(这里简化用宏任务)

  2. 返回新的Promise——实现链式调用的关键

  3. resolvePromise函数——处理各种返回值情况,包括返回普通值、新Promise、甚至thenable对象

第三步:resolvePromise核心解析

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected'))
  }
  
  let called = false
  
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      const 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 (error) {
      if (called) return
      called = true
      reject(error)
    }
  } else {
    resolve(x)
  }
}

这个函数解决了Promise可以resolve任意值的问题——不管是普通值、Promise对象,还是其他实现了then方法的对象,都能正确处理。

第四步:工具方法

// Promise.resolve
static resolve(value) {
  if (value instanceof MyPromise) return value
  return new MyPromise((resolve) => resolve(value))
}

// Promise.reject
static reject(reason) {
  return new MyPromise((_, reject) => reject(reason))
}

// Promise.all
static all(promises) {
  return new MyPromise((resolve, reject) => {
    const results = []
    let count = 0
    promises.forEach((p, index) => {
      MyPromise.resolve(p).then(
        (value) => {
          results[index] = value
          count++
          if (count === promises.length) resolve(results)
        },
        (reason) => reject(reason)
      )
    })
  })
}

// Promise.race
static race(promises) {
  return new MyPromise((resolve, reject) => {
    promises.forEach((p) => {
      MyPromise.resolve(p).then(resolve, reject)
    })
  })
}

写完这个实现,再看async/await,感觉像从看魔术变成了看说明书——原来一切都是有迹可循的。

如果你也在学异步编程,强烈建议手写一遍Promise。代码量不大,但收获是看十篇文章都比不上的。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

评论

欢迎来到我的博客!