Promise详细解析

Promise没出现前

在进行异步编程的时候 常使用回调函数的形式进行异步编程,回调函数层层嵌套就会出现 回调地狱

javascript 复制代码
// 模拟一个异步操作:延迟执行并调用回调
function asyncTask(name, delay, callback) {
  setTimeout(() => {
    console.log(`完成任务: ${name}`)
    callback()
  }, delay)
}

回调地狱 是指 多层嵌套回调函数 的情况,通常表现为:

javascript 复制代码
fs.readFile('user.json', (err, userData) => {
  if (err) {
    console.error('读取用户信息失败:', err);
  } else {
    const userId = JSON.parse(userData).id;
    fs.readFile('posts.json', (err, postsData) => {
      if (err) {
        console.error('读取文章列表失败:', err);
      } else {
        const firstPostId = JSON.parse(postsData)[0].id;
        fs.readFile('comments.json', (err, commentsData) => {
          if (err) {
            console.error('读取评论失败:', err);
          } else {
            console.log('评论内容:', JSON.parse(commentsData));
          }
        });
      }
    });
  }
});
  • 可读性极差:代码横向发展,形成一个丑陋的">"形状,逻辑嵌套混乱,难以阅读和理解。
  • 难以维护:如果想在中间增加一个步骤,或者修改某个步骤的逻辑,简直是一场灾难。
  • 错误处理复杂每个异步操作都需要单独处理错误(if (err)),代码冗余,而且很容易遗漏。
  • 控制流混乱:代码的执行顺序与书写顺序完全不一致,违背了人类的线性思维习惯。

promise出现之后

现在,我们用 Promise 来重写上面的逻辑。假设我们已经有了返回 Promise 的 readFilePromise 函数。

typescript 复制代码
readFilePromise('user.json')
  .then(userData => {
    const userId = JSON.parse(userData).id;
    return readFilePromise('posts.json'); // 返回一个新的 Promise
  })
  .then(postsData => {
    const firstPostId = JSON.parse(postsData)[0].id;
    return readFilePromise('comments.json'); // 再次返回一个新的 Promise
  })
  .then(commentsData => {
    console.log('评论内容:', JSON.parse(commentsData));
  })
  .catch(error => {
    // 统一的错误处理
    console.error('在某个环节出错了:', error);
  });

看看 Promise 带来了什么革命性的改变:

  • 可读性极高 :代码从横向的嵌套,变成了纵向的链式调用 ( .then ) 。逻辑清晰,一目了然,完全符合"做完A,再做B,再做C"的线性思维。
  • 易于维护:想在中间加一个步骤?只需要在链中插入一个新的 .then() 即可。
  • 统一的错误处理 :链中任何一个 Promise 失败(rejected),都会被直接传递到最后的 .catch() 中进行统一处理,无需在每一步都写 if (err)。这让错误处理变得异常简洁和健壮。
  • 状态管理:Promise 自身的状态机制(Pending, Fulfilled, Rejected)使得异步操作的管理变得更加规范和可预测。

Promise 是一个 JavaScript 对象,它代表了一个异步操作的最终完成(或失败)及其结果值。 它是一个容器,用于存放一个未来才会知道结果的事件(通常是异步操作)。

它有三个状态 pendding fulfilled rejected,一旦状态从pendding变成fulfilled 或 rejected之后,状态就不能再改变。

当pedding变成fulfilled之后,对应的.then方法注册的onfulfilled回调函数会执行,并返回一个新的promise(称为promise2)。prmose2的状态和值取决于onfulfilled回调函数的返回值。

场景 1:回调函数返回一个普通值 (非 Promise)

如果你在 onFulfilled 中返回一个数字、字符串、对象等,那么 promise2 会立即变为 fulfilled 状态,并且这个返回值会成为 promise2 的结果。

场景 2:回调函数返回一个新的 Promise (链式调用的核心)

如果你在 onFulfilled 中返回了另一个 Promise (我们称之为 promise3),那么 promise2 的状态将不会 立即确定。 相反,promise2 会"接管" promise3 的状态。也就是说,promise2 会等待 promise3 尘埃落定:

  • 如果 promise3 成功了,promise2 也会成功,并且 promise3 的结果会成为 promise2 的结果。
  • 如果 promise3 失败了,promise2 也会失败,并且 promise3 的失败原因会成为 promise2 的失败原因。
场景 3:回调函数中抛出 (throw) 了一个错误

如果你在 onFulfilled 或 onRejected 中 throw 了一个错误,那么 promise2 会立即变为 rejected 状态,并且这个抛出的错误会成为 promise2 的失败原因。

当pedding变成rejected之后,then方法注册的onrejected回调函数会执行,并返回一个promise(称为promise2)。prmose2的状态和值取决于onrejected回调函数的返回值。

场景 1:返回一个普通值 (非 Promise) - "错误恢复"

这是最常见的错误处理场景。当你在 onRejected 回调中返回一个普通值 (或 undefined),就意味着你已经成功地处理并"消化"了这个错误

  • 结果 :.then() 返回的新 Promise (promise2) 将会变为 fulfilled (成功) 状态,并且你返回的那个普通值,会成为 promise2 的成功结果。
javascript 复制代码
const initialPromise = Promise.reject(new Error("API 请求失败"));
initialPromise
  .then(
    (result) => {
      // onFulfilled: 不会执行
      console.log("成功了:", result);
    },
    (error) => {
      // onRejected: 执行
      console.error("在 then 中捕获到错误:", error.message);
      // 处理错误,并返回一个默认值来"救场"
      return { data: "来自本地缓存的默认数据" }; 
    }
  )
  .then(
    (nextResult) => {
      // 这个 then 的 onFulfilled 会执行!
      console.log("后续操作成功:", nextResult);
      // 输出: 后续操作成功: { data: "来自本地缓存的默认数据" }
    },
    (nextError) => {
      // 这个 then 的 onRejected 不会执行
      console.error("后续操作失败:", nextError);
    }
  );

发生了什么?

  • initialPromise 是 rejected 状态。
  • 第一个 .then() 的 onRejected 被调用。
  • onRejected 返回了一个普通对象。
  • 第一个 .then() 方法本身返回了一个新的、成功的 Promise,其结果就是那个普通对象。
  • 因此,第二个 .then() 的 onFulfilled 被调用。 这个机制就是"错误恢复"。它允许你在捕获错误后,让 Promise 链从失败状态恢复到成功状态,继续执行后续的 ****.then()
场景 2:抛出 (throw) 一个新的错误 - "错误传递"

如果你在 onRejected 回调中处理完错误后,又 throw 了一个新的错误,就意味着你想让这个失败状态继续向下传播

  • 结果 :.then() 返回的新 Promise (promise2) 将会变为 rejected (失败) 状态,并且你抛出的那个新错误,会成为 promise2 的失败原因。
typescript 复制代码
const initialPromise = Promise.reject(new Error("数据库连接失败"));

initialPromise
  .then(
    null, // onFulfilled: 忽略
    (error) => {
      // onRejected: 执行
      console.error("记录日志:", error.message);
      // 包装并抛出一个更具体的错误
      throw new Error("数据层异常,无法继续操作"); 
    }
  )
  .catch( // .catch 等价于 .then(null, onRejected)
    (nextError) => {
      // 这个 catch 会捕获到上面 throw 的新错误
      console.error("最终捕获:", nextError.message);
      // 输出: 最终捕获: 数据层异常,无法继续操作
    }
  );

发生了什么?

  • initialPromise 是 rejected 状态。
  • .then() 的 onRejected 被调用。
  • onRejected 内部 throw 了一个新错误。
  • .then() 方法本身返回了一个新的、失败的 Promise,其原因就是那个新错误。
  • 因此,后续的 .catch() 被调用。 这个机制就是"错误传递"或"错误转换"。它允许你在捕获一个底层错误后,将其包装成一个对上层业务更有意义的错误,再继续抛出。
场景 3:返回一个新的 Promise - "状态接管"

如果你在 onRejected 回调中返回了一个新的 Promise (我们称之为 promise3),那么 .then() 返回的 promise2 的状态将会完全依赖于 promise3 的最终状态。

  • 结果:promise2 会"接管" promise3 的命运。
  • 如果 promise3 最终 fulfilled,promise2 也会 fulfilled。 - 如果 promise3 最终 rejected,promise2 也会 rejected
javascript 复制代码
const initialPromise = Promise.reject(new Error("主API挂了"));

initialPromise
  .then(
    null, // onFulfilled: 忽略
    (error) => {
      // onRejected: 执行
      console.warn("主API失败,尝试调用备用API...", error.message);
      // 返回一个新的 Promise,尝试从备用源获取数据
      return fetchFromBackupAPI(); 
    }
  )
  .then(
    (backupResult) => {
      // 如果 fetchFromBackupAPI() 成功,这里会执行
      console.log("从备用API成功获取数据:", backupResult);
    }
  )
  .catch(
    (finalError) => {
      // 如果 fetchFromBackupAPI() 也失败了,这里会执行
      console.error("主API和备用API都失败了:", finalError);
    }
  );

// 辅助函数
function fetchFromBackupAPI() {
  return new Promise((resolve, reject) => {
    const success = true; // 模拟备用API成功或失败
    setTimeout(() => {
      if (success) {
        resolve({ data: "来自备用服务器的数据" });
      } else {
        reject(new Error("备用API也挂了"));
      }
    }, 500);
  });
}

这个机制非常强大,它允许你在一个异步操作失败后,启动另一个异步操作作为"B计划",并根据"B计划"的结果来决定整个流程的最终走向。

.then() 与 .catch() 的关系

.catch() 方法其实是 .then() 的一个"语法糖",它专门用来处理 rejected 状态。 (then方法中 onRejected函数的语法糖)

finally

finally 是一个用于指定 "无论 Promise 最终状态是成功(fulfilled)还是失败(rejected),都一定会执行" 的回调方法。它的核心作用是统一处理 "清理逻辑" (比如关闭连接、清除定时器、隐藏加载动画等),避免在 thencatch 中重复写相同的代码。

finally 的基础用法

finally 接收一个无参数的回调函数(因为它不关心 Promise 的结果是成功还是失败),且始终返回一个新的 Promise(便于继续链式调用)。

typescript 复制代码
Promise.resolve(/* 成功值 */)
  .then(res => { /* 处理成功逻辑 */ })
  .catch(err => { /* 处理失败逻辑 */ })
  .finally(() => { 
    /* 无论成功/失败,都会执行的清理逻辑 */ 
  });

Promise.resolve()

Promise.resolve() 的行为就像一个智能的包装器。它拿到一个 value 后,会先检查这个 value 的"身份",然后再决定如何包装。

场景 A:value 是一个普通值 (非 Promise, 非 thenable)

这是最简单的情况。

javascript 复制代码
const p = Promise.resolve("Hello");
// 等价于
const p = new Promise(resolve => resolve("Hello"));

它会创建一个新的、立即 fulfilled 的 Promise,并将 "Hello" 作为其结果。

场景 B:value 本身就是一个 Promise

Promise.resolve() 表现得非常"聪明"和"尊重"。

javascript 复制代码
const originalPromise = new Promise(resolve => setTimeout(() => resolve(42), 100));
const p = Promise.resolve(originalPromise);

console.log(p === originalPromise); // 输出: true
  • "该方法会直接返回这个 Promise" :这意味着 p 和 originalPromise 是同一个对象。Promise.resolve() 发现你给它的已经是一个 Promise 了,它就不会多此一举再去创建一个新的 Promise 包装它,而是直接把原来的那个返回给你。这是一种性能优化和行为一致性的体现。

Promise.reject()

与 resolve 的智能和包容不同,reject 的行为非常直接、简单、甚至有点"粗暴"

它的唯一使命就是:创建一个立即失败的 Promise,并且你给我的 reason 是什么,失败的原因就是什么,我绝不多做任何处理。

场景 A:reason 是一个普通值 (Error 对象, 字符串)
typescript 复制代码
const p = Promise.reject(new Error("Something went wrong"));
p.catch(err => {
  console.log(err.message); // 输出: "Something went wrong"
});

这是最常见的用法,非常直接。

场景 B:reason 是一个 Promise (关键区别所在)

现在,我们把一个 Promise 作为失败的原因传进去。

ini 复制代码
// 示例 1: reason 是一个 Promise
const reasonPromise = Promise.resolve("This is a successful promise!");
const p1 = Promise.reject(reasonPromise);

p1.catch(error => {
  console.log(error === reasonPromise); // 输出: true
  // 失败的原因,就是那个 `reasonPromise` 对象本身,而不是它解析后的值 "This is a successful promise!"
  error.then(val => console.log(val)); // 可以调用 .then,输出 "This is a successful promise!"
});

// 示例 2: reason 是一个 thenable
const reasonThenable = {
  then: function(resolve) { resolve("I am a thenable"); }
};
const p2 = Promise.reject(reasonThenable);

p2.catch(error => {
  console.log(error === reasonThenable); // 输出: true
  // 失败的原因,就是 `reasonThenable` 对象本身,它的 .then 方法根本不会被执行
});
  • 它也不会去'展开' :这意味着 Promise.reject() 完全不关心 你传给它的 reason 是什么类型。它不会去检查 reason 是不是 Promise,或者有没有 .then 方法。它直接把这个 reason 原封不动地当作最终的拒绝原因。

async await (像同步编程一样进行异步编程)

async - 声明一个"异步函数"
  • 作用 :async 关键字用于函数声明或函数表达式之前,表示这个函数是一个异步函数
  • 返回值 :一个 async 函数总是隐式地返回一个 Promise
    • 如果函数内部 return 了一个普通值(比如一个数字或字符串),那么这个值会被自动包装在一个 fulfilled (成功) 的 Promise 中。
    • 如果函数内部 throw 了一个错误 ,那么这个错误会被包装在一个 ****rejected (失败) 的 Promise 中。
    • 如果return了一个promise,那么async就返回这个 promise
javascript 复制代码
async function sayHello() {
  return "Hello, World!"; // 实际上返回的是 Promise.resolve("Hello, World!")
}

sayHello().then(console.log); // 输出: Hello, World!

async function throwError() {
  throw new Error("Something went wrong"); // 实际上返回的是 Promise.reject(new Error(...))
}

throwError().catch(console.error); // 输出: Error: Something went wrong
await - "暂停"并等待 Promise 结果
  • 作用await 关键字用于等待一个 Promise 对象的结果。它会"暂停" async 函数的执行,直到这个 Promise 的状态变为 fulfilled rejected
  • 使用限制 :await 只能在 async 函数内部使用。
  • 返回值
    • 如果 Promise 成功 (fulfilled),await 会"解包"这个 Promise,并返回其成功的结果值。
    • 如果 Promise 失败 (rejected),await 会将这个失败的原因(通常是一个 Error 对象)作为异常抛出

并行执行 :await 会让异步操作串行化。如果你有多个互不依赖的异步任务,不要一个接一个地 await,而应该使用 Promise.all() 来让它们并行执行,以提高效率。

Promise的用处

Promise让异步编程不再困难,promise在许多地方都有用,比如 axios,vue Router,所以了解promise的概念和用法很重要。

  1. Promise.all
  2. Promise.race
  3. Promise.any
  4. Promise.allSettled 等方法都指得
相关推荐
jlspcsdn1 分钟前
20251222项目练习
前端·javascript·html
行走的陀螺仪24 分钟前
Sass 详细指南
前端·css·rust·sass
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ29 分钟前
React 怎么区分导入的是组件还是函数,或者是对象
前端·react.js·前端框架
LYFlied43 分钟前
【每日算法】LeetCode 136. 只出现一次的数字
前端·算法·leetcode·面试·职场和发展
子春一244 分钟前
Flutter 2025 国际化与本地化工程体系:从多语言支持到文化适配,打造真正全球化的应用
前端·flutter
QT 小鲜肉1 小时前
【Linux命令大全】001.文件管理之file命令(实操篇)
linux·运维·前端·网络·chrome·笔记
羽沢312 小时前
ECharts 学习
前端·学习·echarts
LYFlied2 小时前
WebAssembly (Wasm) 跨端方案深度解析
前端·职场和发展·wasm·跨端
七月丶2 小时前
实战复盘:我为什么把 TypeScript 写的 CLI 工具用 Rust 重写了一遍?
前端·后端·rust