跳转到内容

类与.then

刀刀

8/8/2025

0 字

0 分钟

Promise 标准

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

  1. 要求
    • ”promise” 是一个拥有 then 方法的对象或函数,其行为符合本规范;
    • “thenable”是一个定义了 then 方法的对象或函数,文中译作“拥有then 方法”;
    • “value”指任何 JavaScript 的合法值(包括undefined, thenable 和 promise);
    • “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')