跳转到内容

.catch与.finally

刀刀

8/8/2025

0 字

0 分钟

Promise A+ 的标准只包含了 .then,并没有包含其余的方法,这些方法都是 ES6 标准中新增的,因此 A+ 规范的文档没有这些事件的描述。推荐去 MDN 查看。

回顾

先来回顾一下之前做了啥,看一下完整的代码:

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')

MyPromise 接收一个回调函数,由于它是同步任务,因此会立即执行这个函数。执行完毕后,会调用 resolvereject 来改变状态。状态只会改变一次,发生改变后不再会改变。如果函数内部抛出了错误,最终状态也会变为 reject,因此用了 try...catch 来捕获错误。

需要注意的是,只能捕获同步的错误,异步的错误无法捕获。

.then 方法返回的是一个新的 MmyPromise,这里不能用旧的是因为旧的 MyPromise 状态已经发生改变了,无法再改变。因此需要返回一个新的 MyPromise,并且根据上一个 MyPromise 的状态来决定下一个 MyPromise 的状态,即链式调用。

.then 的方法是等待状态改变后再执行,因此先用一个数组保存起来,并判断结果是不是一个 Promise,如果是则返回其 .then 后的结果,否则直接返回结果。最后在状态改变后,执行保存的函数。由于 .then 内是微任务,不能同步执行,这里的处理方式是判断当前的环境可用的异步方法,然后调用它。如果都不适用,则采用定时器。

.catch

.catch 方法是 .then 的语法糖,它只接收一个参数,即回调函数 onRejected,之前实现 .then 方法时接收两个参数,参数一函数 onFulfilled 是成功状态执行的;参数二函数 onRejected 是失败状态执行的。因此 .catch 方法可以理解为 .then(null, onRejected)

js
class MyPromise {
  // ...

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

.finally

.finally 方法也是 .then 的语法糖,它接收一个参数,即回调函数 onFinally,无论状态是成功还是失败,都会执行这个函数。但是在执行该回调函数时,是不会接收参数的,它只会把参数传递下去。

js
class MyPromise {
  // ...

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

  finally(onFinally) { 
    return this.then( 
      (res) => { 
        onFinally() 
        return res 
      }, 
      (err) => { 
        onFinally() 
        throw err 
      } 
    ) 
  } 
}