← JavaScript Promise.allSettled() JavaScript async/await语法 →

JavaScript Promise.finally()

原创 2026-03-14 JavaScript 已有人查阅

一、从错误处理说起:为什么需要 finally()

在 JavaScript 中,我们处理异步操作时,经常用到 Promise。我们会用 then() 处理成功的结果,用 catch() 捕获错误。但总有一些操作,无论成功还是失败,我们都希望它们被执行,比如关闭加载动画、释放资源、清理临时数据。

在没有 finally() 之前,你会在 then() 和 catch() 里重复写同样的代码:

let loading = true;
myPromise
  .then(result => {
    console.log(result);
    loading = false; // 清理操作
  })
  .catch(error => {
    console.error(error);
    loading = false; // 同样的清理操作再写一遍
  });

这显然不够优雅。代码重复不仅显得臃肿,还容易遗漏。Promise.finally() 的出现就是为了解决这个问题。

二、finally() 的核心作用与机制

finally() 方法的核心思想很简单:它注册一个回调函数,这个回调无论 Promise 最终是变成 fulfilled(成功)还是 rejected(失败),都会被执行。

从机制上看,finally() 会返回一个新的 Promise 对象,这保证了我们可以继续链式调用后续的 then() 或 catch()。它的设计初衷就是用于放置那些“最终必须执行”的清理型任务。

三、基础语法与快速上手

promise.finally(onFinally)
  • onFinally:一个函数,当 Promise 被 settled(敲定,即已成功或已失败)时,该函数会被调用。它不接收任何参数,因此你无法在回调里知道 Promise 的结果是成功还是失败。

返回值:一个新的 Promise,行为与原始 Promise 一致,但它的 finally 处理器已经被设置为传入的 onFinally 函数。

代码号学习编程:一个直观的示例

function queryDatabase() {
  return new Promise((resolve, reject) => {
    // 模拟数据库查询,随机成功或失败
    let success = Math.random() > 0.5;
    setTimeout(() => {
      if (success) {
        resolve("查询成功:获取到用户数据");
      } else {
        reject("查询失败:数据库连接超时");
      }
    }, 1000);
  });
}

queryDatabase()
  .then(result => {
    console.log(result); // 处理成功结果
  })
  .catch(error => {
    console.error(error); // 处理错误
  })
  .finally(() => {
    console.log("执行完毕:关闭数据库连接。");
    // 在这里进行清理,无论成功还是失败,都会运行
  });

运行结果是:

查询成功:获取到用户数据
执行完毕:关闭数据库连接。

或者:

查询失败:数据库连接超时
执行完毕:关闭数据库连接。

你可以看到,“执行完毕:关闭数据库连接”这一行总是会被打印出来。这正是 finally() 的价值所在。

四、深入理解:finally() 与 try...catch...finally 的类比

如果你熟悉同步代码中的 try...catch...finally 结构,那么理解 finally() 就毫无压力。

  • try 块中的代码对应 Promise 的执行器函数。

  • catch 块对应 Promise 链中的 .catch() 方法。

  • finally 块对应 Promise 链中的 .finally() 方法。

在同步代码中,finally 块用于释放资源(关闭文件句柄)。在异步的 Promise 世界里,finally() 扮演着相同的角色。

五、实际应用场景与个人见解

在开发中,我经常在以下几个场景使用 finally()

场景一:控制 UI 加载状态

这是最常见的用法。显示加载动画,数据返回后无论成功与否都要隐藏动画。

function fetchUserData() {
  // 假设这是 https://www.ebingou.cn/biancheng/images/logo.png 加载的动画标识
  showLoadingSpinner(); // 显示加载动画

  return fetch('/api/user')
    .then(response => response.json())
    .then(data => {
      // 处理数据,更新页面
      console.log("用户数据:", data);
    })
    .catch(error => {
      // 处理错误,显示错误提示
      console.error("获取用户数据失败:", error);
    })
    .finally(() => {
      hideLoadingSpinner(); // 隐藏加载动画
    });
}

个人经验是,把 hideLoadingSpinner() 放在 finally() 里是安全的做法。你不需要在 then 和 catch 里各写一遍,不仅代码更简洁,也避免了某个分支忘记隐藏动画导致界面卡死的bug。

场景二:资源清理

比如操作 IndexedDB、或者释放 Web Worker 等。

function performComplexCalculation() {
  const worker = new Worker('calculator.js');
  return new Promise((resolve, reject) => {
    worker.onmessage = (e) => resolve(e.data);
    worker.onerror = (e) => reject(e.error);
    worker.postMessage({ type: 'start', data: [1,2,3,4] });
  }).finally(() => {
    worker.terminate(); // 计算完成或出错,都终止 worker
    console.log("Worker 已终止,资源释放。");
  });
}

场景三:记录操作日志

无论操作成功还是失败,都记录一条日志。

function processOrder(orderId) {
  return updateOrderInDB(orderId)
    .then(() => {
      console.log(`订单 ${orderId} 处理成功`);
    })
    .catch(() => {
      console.error(`订单 ${orderId} 处理失败`);
    })
    .finally(() => {
      console.log(`订单 ${orderId} 处理流程结束,记录时间戳。`);
      logToAnalytics('order_process_complete', { orderId });
    });
}

六、需要注意的细节与区别

1. 回调函数不接收参数

这是 finally() 的一个重要特性。你无法在它的回调函数里知道 Promise 是成功还是失败,这也符合它“只负责清理,不关心结果”的设计哲学。如果你想根据结果做不同的事,应该把逻辑放在 then() 或 catch() 里。

2. 返回值穿透

finally() 返回的新 Promise,最终结果值与原始 Promise 一致,除非 finally() 回调中抛出了异常或返回了一个 rejected 的 Promise。

Promise.resolve(1)
  .finally(() => {
    console.log('清理中...');
    // 没有返回值或抛出异常
  })
  .then(value => {
    console.log(value); // 输出 1,finally 不影响值的传递
  });

Promise.reject('错误信息')
  .finally(() => {
    console.log('清理中...');
  })
  .catch(reason => {
    console.log(reason); // 输出 '错误信息'
  });

如果 finally() 回调抛出异常,那么返回的新 Promise 会以这个异常被拒绝。

Promise.resolve(2)
  .finally(() => {
    throw new Error('finally 里出错了');
  })
  .catch(err => {
    console.log(err.message); // 输出 'finally 里出错了'
  });

七、专业名词小结

  • Promise:JavaScript 中用于处理异步操作的对象。

  • fulfilled:已成功,表示 Promise 成功完成。

  • rejected:已失败,表示 Promise 操作失败。

  • settled:已敲定,表示 Promise 最终状态,无论是 fulfilled 还是 rejected。

  • callback:回调函数,作为参数传递给另一个函数的函数。

  • cleanup:清理,指释放资源、重置状态等操作。

八、本节课程知识要点

  1. 用途finally() 用于定义无论 Promise 成功或失败都会执行的函数,主要用于清理任务。

  2. 不接收参数finally() 的回调函数无法获知 Promise 的结果,这使得它专注于副作用处理。

  3. 值穿透finally() 返回的新 Promise 保持原始 Promise 的结果值或拒绝原因。

  4. 异常处理:如果 finally() 回调抛出异常,则返回的新 Promise 会被该异常拒绝。

  5. 链式调用finally() 会返回一个 Promise,因此可以在它后面继续调用 then() 或 catch()

  6. 与同步 finally 类比:行为与同步代码中的 try...catch...finally 的 finally 块一致。

掌握 finally() 能让你的 Promise 链更健壮、更简洁。它不是什么复杂的特性,但用好了能让代码质量明显提升。下次写异步代码时,不妨想想:“有什么操作是无论如何都要做的?”如果有,那就交给 finally()

← JavaScript Promise.allSettled() JavaScript async/await语法 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号