跳转到内容

类与 .then

刀刀
0字
0分钟
2026/8/16

Promise 标准

具体Promise A+标准可查看 Promise A+ ,这里只拿几个点做简要提及:

  1. 要求
    • "promise" 是一个拥有 then 方法的对象或函数,其行为符合本规范;
    • "thenable" 是一个定义了 then 方法的对象或函数,文中译作"拥有 then 方法" ;
    • "value" 指任何JavaScript的合法值(包括 undefined, thenablepromise);
    • "exception" 是使用 throw 语句抛出的一个值。
    • "reason" 表示一个 promise 的拒绝原因。
  2. 状态
    • 处于等待态时,Promise需满足以下条件:可以迁移至完成态或拒绝态
    • 处于完成态时,Promise需满足以下条件:不能迁移至其他任何状态;必须拥有一个不可变的终值
    • 处于拒绝态时,Promise需满足以下条件:不能迁移至其他任何状态;必须拥有一个不可变的拒绝原因

类的搭建

基础实现

在使用时都是通过 new Promise 来创建一个 Promise,因此需要创建一个类 MyPromise

允许一个函数作为参数,注意,原生的 Promise 回调函数内的代码是同步的,因此这里直接调用。

js
class MyPromise {
  constructor(executor) {
    executor()
  }
}

const p = new MyPromise((resolve, reject) => {})

状态

根据标准,Promise 有三种状态:pendingfulfilledrejected,并且状态只能从 pendingfulfilledrejected,之后不能再改变。

修改状态是通过参数 resolvereject 来实现的,这两个方法都支持接收一个参数。综上所述类新增功能如下:

  • 新增状态属性 status,初始值为 pending
  • 新增值 value,初始值为 undefined
  • 新增方法 resolve,接收一个参数,当状态为 pending 时,将状态改为 fulfilled,并将参数赋值给 value;如果不是 pending 状态,则不做任何操作
  • 新增方法 reject,接收一个参数,当状态为 pending 时,将状态改为 rejected,并将参数赋值给 value;如果不是 pending 状态,则不做任何操作
js
class MyPromise {
  state = 'pending'
  value 
  constructor(executor) {
    const resolve = (val) => {
      if (this.state !== 'pending') return
      this.value = val 
      this.state = 'fulfilled'
    } 

    const reject = (reason) => {
      if (this.state !== 'pending') return
      this.value = reason 
      this.state = 'rejected'
    } 

    executor() 
    executor(resolve, reject) 
  }
}

const p = new MyPromise((resolve, reject) => {
  resolve(1) 
})

优化

初步实现了 Promise 的状态管理,不过需要考虑以下几点:

  1. 如果 new MyPromise 时传入的 executor 抛出异常,则 MyPromise 状态应设置为 rejected,并且 value 为抛出的异常。查看原生 Promise 也是如此的操作

    原生  操作

  2. 状态 state 应该是内部变量,不应该让外部直接拿到使用修改,因此需要修改为内部私有变量

  3. resolvereject 内部相同的代码较多,可以抽离封装成一个公共函数

  4. 状态如果直接写,后续可能会修改,维护起来很麻烦,最好做成一个常量

根据上方的考虑,修改代码如下:

js
// 常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  state = 'pending'
  #state = PENDING // 修改为内部私有
  #value
  constructor(executor) {
    const resolve = (val) => {
      if (this.state !== 'pending') return
      this.value = val 
      this.state = 'fulfilled'
      this.#setState(FULFILLED, val) 
    }

    const reject = (reason) => {
      if (this.state !== 'pending') return
      this.value = reason 
      this.state = 'rejected'
      this.#setState(REJECTED, reason) 
    }

    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err) 
    } 
  }

  // 修改状态和值
  #setState(state, value) {
    if (this.#state !== PENDING) return
    this.#value = value 
    this.#state = state 
  } 
}

const p = new MyPromise((resolve, reject) => {
  resolve(1)
})

console.log(p.#state) // 报错,不可访问私有属性

现在能够解决上述罗列的几个优化点了。不过要注意的是,try...catch 是无法捕获异步的报错,这个原生 Promise 也是如此。

原生  报错

then 方法

基本实现

实现 then 方法,主要功能是接收两个函数参数,当 Promise 状态为 fulfilled 时,执行第一个函数,并传入 value;当 Promise 状态为 rejected 时,执行第二个函数,并传入 reason

新增一个 then 方法,接收两个参数 onFulfilledonRejected,判断当前的状态 #state,为 fulfilled 时,执行 onFulfilled,并传入 value;为 rejected 时,执行 onRejected,并传入 reason

js
// 常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  #state = PENDING // 修改为内部私有
  #value
  constructor(executor) {
    const resolve = (val) => {
      this.#setState(FULFILLED, val)
    }

    const reject = (reason) => {
      this.#setState(REJECTED, reason)
    }

    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  // 修改状态和值
  #setState(state, value) {
    if (this.#state !== PENDING) return
    this.#value = value
    this.#state = state
  }

  then(onFulfilled, onRejected) {
    if (this.#state === FULFILLED) {
      onFulfilled(this.#value) 
    } 
    else if (this.#state === REJECTED) {
      onRejected(this.#value) 
    } 
  } 
}

const p = new MyPromise((resolve, reject) => {
  resolve(1)
})

p.then((res) => {
  console.log(res) // 1
})

链式调用

原生 Promise 是可以通过 .then 链式调用的,参数是上一个 .then 返回的值,而如果直接 return this 返回当前的 MyPromise 类是无法实现功能的。因为当前的 MyPromise 状态已经不是 pending

所以我们需要在 then 方法中返回一个新的 Promise,并且将 onFulfilledonRejected 的返回值作为参数传递给新的 Promise

拿到新值后,作为参数调用 resolve 更新 MyPromise 的状态和值。注意,无论是成功还是失败,都是调用的 resolve

js
// 常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  #state = PENDING // 修改为内部私有
  #value
  constructor(executor) {
    const resolve = (val) => {
      this.#setState(FULFILLED, val)
    }

    const reject = (reason) => {
      this.#setState(REJECTED, reason)
    }

    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  // 修改状态和值
  #setState(state, value) {
    if (this.#state !== PENDING) return
    this.#value = value
    this.#state = state
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      if (this.#state === FULFILLED) {
        const res = onFulfilled(this.#value) 
        resolve(res) 
      } else if (this.#state === REJECTED) {
        const res = onRejected(this.#value) 
        resolve(res) 
      }
    }) 
  }
}

const p = new MyPromise((resolve, reject) => {
  resolve(1)
})

p.then((res) => {
  console.log(res) // 1
})

考虑异步

js
const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 20)
})

基本实现版本,只能处理同步代码,如果 Promise 是异步的,则无法处理,这是因为前面写的判断只判断了 fulfilledrejected 两种状态,并没有处理 pending 状态。而异步代码执行时,Promise 的状态是 pending,因此需要处理 pending 状态。

如果是 pending 状态,则将 onFulfilledonRejected 函数保存起来,等到状态变为 fulfilledrejected 时,再执行。

那么在哪里才能知道 Promise 的状态变为 fulfilledrejected 呢?在 resolvereject 函数中,也就是一开始抽离出来的 #setState 函数。在那里再调用前面保存的 onFulfilledonRejected 函数即可。

js
// 常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  #state = PENDING // 修改为内部私有
  #value
  #handler // 保存onFulfilled和onRejected函数
  constructor(executor) {
    const resolve = (val) => {
      this.#setState(FULFILLED, val)
    }

    const reject = (reason) => {
      this.#setState(REJECTED, reason)
    }

    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  // 修改状态和值
  #setState(state, value) {
    if (this.#state !== PENDING) return
    this.#value = value
    this.#state = state
    this.#handler() 
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#handler = () => {
        if (this.#state === FULFILLED) {
          const res = onFulfilled(this.#value)
          resolve(res)
        } else if (this.#state === REJECTED) {
          const res = onRejected(this.#value)
          resolve(res)
        }
      } 
      if (this.#state !== PENDING) this.#handler() 
    })
  }
}

const p = new MyPromise((resolve, reject) => {
  resolve(1)
})

p.then((res) => {
  console.log(res) // 1
})

多个实例

接下来看一个场景:

js
const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 1000)
})

p.then((res) => {
  console.log('p1', res)
})

p.then((res) => {
  console.log('p2', res)
})

现在只能打印 p2p1 没有打印。这是因为最开始运行第一个 thenthis.#handler 保存了 p1 的回调;然后运行到 p2 后重新赋值,覆盖了 p1 的回调。所以需要把 this.#handler 改成数组,保存多个回调。

js
// 常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  #state = PENDING; // 修改为内部私有
  #value;
  #handler; // 保存onFulfilled和onRejected函数
  #handlers = []; // 保存onFulfilled和onRejected函数
  constructor(executor) {
    const resolve = (val) => {
      this.#setState(FULFILLED, val);
    }

    const reject = (reason) => {
      this.#setState(REJECTED, reason);
    }

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  // 修改状态和值
  #setState (state, value) {
    if (this.#state !== PENDING) return;
    this.#value = value;
    this.#state = state;
    this.#handler(); 
    this.#runTask(); 
  }

  #runTask () {
    if (this.#state !== PENDING) { 
      this.#handlers().forEach((cb) => cb()); 
      this.#handlers = []; 
    } 
  }

  then (onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#handler = () => { 
      this.#handlers.push(() => { 
        if (this.#state === FULFILLED) {
          const res = onFulfilled(this.#value);
          resolve(res);
        }
        else if (this.#state === REJECTED) {
          const res = onRejected(this.#value);
          resolve(res);
        }
      } 
      }) 
      if (this.#state !== PENDING) this.#handler(); 
      this.#runTask(); 
    })
  }
}

const p = new MyPromise((resolve, reject) => {
  resolve(1);
});

p.then(res => {
  console.log('p1', res)
})

p.then(res => {
  console.log('p2', res)
})

优化

下面有几个小点需要优化一下:

  • 功能优化:需要考虑 return new MyPromise 中代码报错的情况,需要和前面一样添加 try...catch

  • 代码优化:if...else 里面的代码重复了,可以封装一下。

  • 根据 A+ 规范,.then 第一个参数可以不传,下一个 .then 接收的还是上一个 promise 的返回值。

    A+ 规范

js
// 常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  #state = PENDING // 修改为内部私有
  #value
  #handlers = [] // 保存onFulfilled和onRejected函数
  constructor(executor) {
    const resolve = (val) => {
      this.#setState(FULFILLED, val)
    }

    const reject = (reason) => {
      this.#setState(REJECTED, reason)
    }

    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  // 修改状态和值
  #setState(state, value) {
    if (this.#state !== PENDING) return
    this.#value = value
    this.#state = state
    this.#runTask()
  }

  #runTask() {
    if (this.#state !== PENDING) {
      this.#handlers().forEach((cb) => cb())
      this.#handlers = []
    }
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#handlers.push(() => {
        if (this.#state === FULFILLED) {
          const res = onFulfilled(this.#value) 
          resolve(res) 
        } 
        else if (this.#state === REJECTED) {
          const res = onRejected(this.#value) 
          resolve(res) 
        } 
        try {
          const cb = this.#state === FULFILLED ? onFulfilled : onRejected 
          // 如果cb不是函数,说明用户没传,直接返回结果
          const res = typeof cb === 'function' ? cb(this.#value) : this.#value 
          resolve(res) 
        } catch (err) {
          reject(err) 
        } 
      })
      this.#runTask()
    })
  }
}

const p = new MyPromise((resolve, reject) => {
  resolve(1)
})

p.then((res) => {
  console.log('p1', res)
})

p.then((res) => {
  console.log('p2', res)
})

异步处理

现在的代码写的差不多了,开始查看细节部分。先看一个例子:

js
const p = new MyPromise((resolve, reject) => {
  resolve(1)
})

p.then((res) => {
  console.log('then', res)
})

console.log('end')

查看上方的打印,.then 里面的代码应该是异步的,因此会先打印 end,再打印 then。但是现在的代码是同步的,因此会先打印 then,再打印 end

前面所有事件处理的逻辑,都是在 #runTask 函数里面执行的,而目前该方法都是同步的,因此需要将 #runTask 函数改成异步的。

想要实现异步的效果,可以用到 queueMicrotask,但是它有兼容性问题,可以写一个函数,做兼容性处理。

新建一个函数,判断当前环境 queueMicrotask 类型是否是一个函数,如果是说明该方法可用,直接使用;如果不是,则判断当前环境是否是NodeJS,如果是则可以使用 process.nextTick;如果是浏览器环境则没有 process,可以改用 MutationObserver;如果都没有,就只能使用 setTimeout

js
// 常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function runMicrotasks(fn) {
  if (typeof queueMicrotask === 'function') {
    queueMicrotask(fn) 
  } 
  else if (
    typeof process === 'object' &&
    typeof process.nextTick === 'function'
  ) {
    process.nextTick(fn) 
  } 
  else if (typeof MutationObserver === 'function') {
    const observer = new MutationObserver(fn) 
    const textNode = document.createTextNode(String(Math.random())) 
    observer.observe(textNode, { characterData: true }) 
    // 当节点的内容发生变化,就会异步执行前面的fn函数
    textNode.data = String(Math.random()) 
  } 
  else {
    setTimeout(fn, 0) 
  } 
} 

class MyPromise {
  #state = PENDING // 修改为内部私有
  #value
  #handlers = [] // 保存onFulfilled和onRejected函数
  constructor(executor) {
    const resolve = (val) => {
      this.#setState(FULFILLED, val)
    }

    const reject = (reason) => {
      this.#setState(REJECTED, reason)
    }

    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  // 修改状态和值
  #setState(state, value) {
    if (this.#state !== PENDING) return
    this.#value = value
    this.#state = state
    this.#runTask()
  }

  #runTask() {
    runMicrotasks(() => {
      if (this.#state !== PENDING) {
        this.#handlers().forEach((cb) => cb())
        this.#handlers = []
      }
    }) 
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#handlers.push(() => {
        try {
          const cb = this.#state === FULFILLED ? onFulfilled : onRejected
          const res = typeof cb === 'function' ? cb(this.#value) : this.#value
          resolve(res)
        } catch (err) {
          reject(err)
        }
      })
      this.#runTask()
    })
  }
}

const p = new MyPromise((resolve, reject) => {
  resolve(1)
})

p.then((res) => {
  console.log('p1', res)
})

console.log('end')

.then 返回 Promise

接下来还要考虑一个场景,就是 .thenreturn new Promise 的情况,示例代码如下:

js
const p = new MyPromise((resolve, reject) => {
  resolve(1)
})

const p1 = p.then(() => {
  return new Promise((resolve, reject) => {
    resolve(2)
  })
})

p1.then((res) => {
  console.log('p1', res) // p1 { Promise 2 }
})

const pro = new Promise((resolve, reject) => {
  resolve(1)
})

const pro1 = pro.then(() => {
  return new Promise((resolve, reject) => {
    resolve(2)
  })
})

pro1.then((res) => {
  console.log('pro1', res) // pro1 2
})

原生 Promise 返回的是其结果,而我们封装的返回的还是一个 Promise,因此需要修改一下代码,具体要修改的是 then 方法。

原先的代码是直接 resolve(res); 返回结果,现在要先判断一下 res 是否是一个 Promise,如果是返回其 .then 的结果;如果不是,则原样返回。

那么问题来了,如何判断 res 是否是一个 Promise 呢?根据 A+ 规范的说法是,只要这个函数有 .then 方法,那么就默认他是 Promise 函数。

js
// 常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function runMicrotasks(fn) {
  if (typeof queueMicrotask === 'function') {
    queueMicrotask(fn)
  } else if (
    typeof process === 'object' &&
    typeof process.nextTick === 'function'
  ) {
    process.nextTick(fn)
  } else if (typeof MutationObserver === 'function') {
    const observer = new MutationObserver(fn)
    const textNode = document.createTextNode(String(Math.random()))
    observer.observe(textNode, { characterData: true })
    // 当节点的内容发生变化,就会异步执行前面的fn函数
    textNode.data = String(Math.random())
  } else {
    setTimeout(fn, 0)
  }
}

function isPromiseLike(obj) {
  return typeof obj?.then === 'function'
} 

class MyPromise {
  #state = PENDING // 修改为内部私有
  #value
  #handlers = [] // 保存onFulfilled和onRejected函数
  constructor(executor) {
    const resolve = (val) => {
      this.#setState(FULFILLED, val)
    }

    const reject = (reason) => {
      this.#setState(REJECTED, reason)
    }

    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  // 修改状态和值
  #setState(state, value) {
    if (this.#state !== PENDING) return
    this.#value = value
    this.#state = state
    this.#runTask()
  }

  #runTask() {
    runMicrotasks(() => {
      if (this.#state !== PENDING) {
        this.#handlers().forEach((cb) => cb())
        this.#handlers = []
      }
    })
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#handlers.push(() => {
        try {
          const cb = this.#state === FULFILLED ? onFulfilled : onRejected
          const res = typeof cb === 'function' ? cb(this.#value) : this.#value
          if (isPromiseLike(res)) {
            resolve(res.then(resolve, reject)) 
          } 
          else {
            resolve(res)
          } 
        } catch (err) {
          reject(err)
        }
      })
      this.#runTask()
    })
  }
}

const p = new MyPromise((resolve, reject) => {
  resolve(1)
})

p.then((res) => {
  console.log('p1', res)
})

console.log('end')

贡献者

The avatar of contributor named as duyidao duyidao
The avatar of contributor named as 刀刀 刀刀
The avatar of contributor named as v_duyilin v_duyilin

页面历史

刀刀博客累计访客 人;文档累计访问量共