什么是 Promise

Promise 是 JavaScript 中处理异步操作的一种方式,它代表了一个异步操作的最终完成(或失败)及其结果值。Promise 可以让我们避免陷入回调地狱(Callback Hell),使异步代码更加清晰和易于管理。

Promise 对象有三种状态:

  1. Pending(进行中):初始状态,既没有被兑现,也没有被拒绝
  2. Fulfilled(已成功):操作成功完成
  3. Rejected(已失败):操作失败

Promise 的状态一旦改变,就不会再变,这保证了其结果的可靠性。

Promise 的基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve(value); // 将Promise状态改为fulfilled
} else {
reject(error); // 将Promise状态改为rejected
}
});

// 使用Promise
promise
.then(result => {
// 处理成功的结果
})
.catch(error => {
// 处理失败的情况
});

为什么可以一直 .then

Promise 的链式调用是其最重要的特性之一。.then() 方法会返回一个新的 Promise 对象,这就是为什么可以一直链式调用 .then() 的原因。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
promise
.then((result) => {
console.log(result);
return result + 1; // 返回一个值
})
.then((result) => {
console.log(result);
return new Promise((resolve) => {
// 返回一个新的Promise
setTimeout(() => resolve(result + 1), 1000);
});
})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});

每次调用 .then() 都会创建一个新的 Promise,这个新的 Promise 的状态取决于 .then() 回调函数的返回值:

  1. 如果返回一个普通值(非 Promise),新 Promise 会立即变为 fulfilled 状态
  2. 如果返回一个 Promise,新 Promise 会采用这个返回的 Promise 的状态
  3. 如果抛出异常,新 Promise 会变为 rejected 状态

Promise 的使用场景

1. 异步请求处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用 fetch API 获取数据
function fetchData(url) {
return fetch(url)
.then((response) => response.json())
.then((data) => {
console.log("数据获取成功:", data);
return data;
})
.catch((error) => {
console.error("数据获取失败:", error);
throw error;
});
}

fetchData("https://api.example.com/users").then((users) => {
// 处理用户数据
console.log(users);
});

2. 多个异步操作的并行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 同时获取多个资源
Promise.all([
fetch("/api/users").then((res) => res.json()),
fetch("/api/posts").then((res) => res.json()),
fetch("/api/comments").then((res) => res.json()),
])
.then(([users, posts, comments]) => {
// 所有数据都获取成功后执行
console.log("用户数据:", users);
console.log("文章数据:", posts);
console.log("评论数据:", comments);
})
.catch((error) => {
// 任何一个请求失败都会执行这里
console.error("请求失败:", error);
});

3. 异步操作的顺序执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 顺序执行异步操作
function step1() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("步骤1完成");
resolve("result1");
}, 1000);
});
}

function step2(data) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("步骤2完成,接收到:", data);
resolve("result2");
}, 1000);
});
}

function step3(data) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("步骤3完成,接收到:", data);
resolve("result3");
}, 1000);
});
}

// 链式调用确保顺序执行
step1()
.then((result) => step2(result))
.then((result) => step3(result))
.then((result) => {
console.log("所有步骤完成,最终结果:", result);
});

4. 错误处理和重试机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 带重试机制的异步操作
function fetchWithRetry(url, retries = 3) {
return fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
})
.catch((error) => {
if (retries > 0) {
console.log(`请求失败,剩余重试次数: ${retries}`);
return fetchWithRetry(url, retries - 1);
}
throw error;
});
}

fetchWithRetry("/api/data")
.then((data) => console.log("数据获取成功:", data))
.catch((error) => console.error("所有重试都失败:", error));

Promise 解决了哪些问题

1. 回调地狱(Callback Hell)

在 Promise 出现之前,处理多个嵌套的异步操作会导致代码难以阅读和维护:

1
2
3
4
5
6
7
8
9
10
11
// 回调地狱示例
getData(function (a) {
getMoreData(a, function (b) {
getEvenMoreData(b, function (c) {
getEvenEvenMoreData(c, function (d) {
// 嵌套越来越深
console.log(d);
});
});
});
});

使用 Promise 后可以改为链式调用:

1
2
3
4
5
6
7
8
9
10
11
// 使用 Promise 链式调用
getData()
.then((a) => getMoreData(a))
.then((b) => getEvenMoreData(b))
.then((c) => getEvenEvenMoreData(c))
.then((d) => {
console.log(d);
})
.catch((error) => {
console.error(error);
});

2. 统一的错误处理

Promise 提供了统一的错误处理机制,可以通过 .catch() 捕获整个链中的任何错误:

1
2
3
4
5
6
7
8
9
10
11
promise1()
.then((result) => promise2(result))
.then((result) => promise3(result))
.then((result) => {
// 处理最终结果
console.log(result);
})
.catch((error) => {
// 统一处理所有可能的错误
console.error("操作失败:", error);
});

3. 更好的控制流

Promise 提供了多种控制流方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Promise.all - 并行执行多个 Promise,全部成功才成功
Promise.all([promise1, promise2, promise3]).then((results) =>
console.log("全部成功:", results)
);

// Promise.race - 并行执行多个 Promise,第一个完成就完成
Promise.race([promise1, promise2, promise3]).then((result) =>
console.log("第一个完成:", result)
);

// Promise.allSettled - 等待所有 Promise 完成(无论成功或失败)
Promise.allSettled([promise1, promise2, promise3]).then((results) =>
console.log("所有都已完成:", results)
);

Promise 的局限性

虽然 Promise 解决了很多异步编程的问题,但它也有一些局限性:

  1. 无法取消:一旦创建,Promise 就无法取消
  2. 内部错误不会暴露:如果在 Promise 内部抛出错误但没有处理,可能会被静默忽略
  3. 无法获取进度信息:Promise 只有完成或失败两种状态,无法获取进度信息

总结

Promise 是 JavaScript 异步编程的重要进步,它解决了回调地狱问题,提供了更清晰的错误处理机制,并支持更好的控制流。通过理解 Promise 的原理和使用方法,我们可以编写出更优雅、更易维护的异步代码。

随着 ES2017 中 async/await 的引入,异步编程变得更加直观,但理解 Promise 仍然是掌握现代 JavaScript 的基础。无论是使用原生 Promise 还是 async/await,它们本质上都是基于 Promise 的,因此深入理解 Promise 对于每个 JavaScript 开发者来说都是必不可少的。