实现 Promise.prototype.finally

手写实现 Promise 的 finally 方法,无论 Promise 成功或失败都会执行回调

问题

Promise.prototype.finally 是 ES2018 引入的方法,用于在 Promise 结束时(无论成功或失败)执行指定的回调函数。需要实现一个符合规范的 finally 方法,要求:

  1. 无论 Promise 是 fulfilled 还是 rejected,finally 回调都会执行
  2. finally 回调不接收任何参数
  3. finally 返回一个新的 Promise,会传递原 Promise 的结果
  4. 如果 finally 回调返回一个 rejected Promise,则以该 rejected Promise 的原因作为新 Promise 的拒绝原因

解答

Promise.prototype.finally = function(callback) {
  return this.then(
    // 成功时的处理
    value => {
      // 执行 callback,并等待其完成(如果返回 Promise)
      return Promise.resolve(callback()).then(() => value);
    },
    // 失败时的处理
    reason => {
      // 执行 callback,并等待其完成(如果返回 Promise)
      return Promise.resolve(callback()).then(() => {
        throw reason;
      });
    }
  );
};

更简洁的实现方式:

Promise.prototype.finally = function(callback) {
  const P = this.constructor;
  
  return this.then(
    value => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason; })
  );
};

使用示例

// 示例 1:基本使用
Promise.resolve('成功')
  .finally(() => {
    console.log('清理工作');
  })
  .then(value => {
    console.log(value); // 输出: 成功
  });

// 示例 2:失败情况
Promise.reject('失败')
  .finally(() => {
    console.log('无论成功失败都执行');
  })
  .catch(error => {
    console.log(error); // 输出: 失败
  });

// 示例 3:finally 返回 Promise
Promise.resolve('原始值')
  .finally(() => {
    return new Promise(resolve => {
      setTimeout(() => {
        console.log('异步清理完成');
        resolve();
      }, 1000);
    });
  })
  .then(value => {
    console.log(value); // 1秒后输出: 原始值
  });

// 示例 4:finally 中抛出错误
Promise.resolve('成功')
  .finally(() => {
    throw new Error('finally 中的错误');
  })
  .catch(error => {
    console.log(error.message); // 输出: finally 中的错误
  });

// 示例 5:实际应用场景 - 加载状态管理
function fetchData() {
  let loading = true;
  console.log('开始加载...');
  
  return fetch('https://api.example.com/data')
    .then(response => response.json())
    .finally(() => {
      loading = false;
      console.log('加载完成,关闭 loading');
    });
}

关键点

  • 使用 then 方法实现:finally 本质上是对 then 方法的封装,同时处理成功和失败两种情况

  • 不接收参数:finally 的回调函数不接收任何参数,因为它不关心 Promise 的结果

  • 透传原始值:无论成功还是失败,都需要将原始的 value 或 reason 传递给下一个 then/catch

  • Promise.resolve 包装:使用 Promise.resolve(callback()) 确保即使 callback 返回非 Promise 值也能正确处理

  • 保持原始状态:成功时返回原始 value,失败时重新抛出原始 reason

  • 支持异步回调:如果 callback 返回 Promise,会等待该 Promise 完成后再传递原始结果

  • 错误优先级:如果 finally 回调中抛出错误或返回 rejected Promise,该错误会覆盖原始结果

  • 构造函数引用:使用 this.constructor 而不是直接使用 Promise,以支持 Promise 的子类