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),都一定会执行" 的回调方法。它的核心作用是统一处理 "清理逻辑" (比如关闭连接、清除定时器、隐藏加载动画等),避免在 then
和 catch
中重复写相同的代码。
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的概念和用法很重要。
- Promise.all
- Promise.race
- Promise.any
- Promise.allSettled 等方法都指得