JavaScript Promise深入解析与实战技巧

JavaScript Promise深入解析与实战技巧

Promise

Promise 是 JavaScript 中处理异步操作的重要机制,掌握它对于编写高质量的异步代码至关重要。

一、Promise 基础概念

1.1 什么是 Promise

Promise 是一个代表异步操作最终完成或失败的对象。它有三种状态:

1
2
3
4
// Promise 的三种状态
const pending = 'pending'; // 进行中
const fulfilled = 'fulfilled'; // 已成功
const rejected = 'rejected'; // 已失败

特点:

  • 状态不可逆,一旦改变就不会再变
  • 只有异步操作的结果可以决定当前状态

1.2 创建 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 基本用法
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('操作成功');
} else {
reject('操作失败');
}
}, 1000);
});

// 使用 then 和 catch
promise
.then(result => console.log(result))
.catch(error => console.error(error));

二、Promise 的链式调用

2.1 then 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 链式调用示例
fetchUser()
.then(user => {
console.log('用户信息:', user);
return fetchPosts(user.id); // 返回新的 Promise
})
.then(posts => {
console.log('用户文章:', posts);
return fetchComments(posts[0].id);
})
.then(comments => {
console.log('文章评论:', comments);
})
.catch(error => {
console.error('发生错误:', error);
});

2.2 then 的返回值

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
// then 可以返回三种值
Promise.resolve(1)
.then(value => {
// 1. 返回普通值,会被包装成 Promise.resolve(value)
return value + 1;
})
.then(value => {
console.log(value); // 2

// 2. 返回 Promise,会等待其完成
return Promise.resolve(value + 1);
})
.then(value => {
console.log(value); // 3

// 3. 抛出错误,会被下一个 catch 捕获
throw new Error('出错了');
})
.catch(error => {
console.error(error.message); // 出错了

// catch 返回的值会传递给下一个 then
return '恢复后的值';
})
.then(value => {
console.log(value); // 恢复后的值
});

三、Promise 静态方法

3.1 Promise.all

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
// 等待所有 Promise 完成
const promises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
];

Promise.all(promises)
.then(results => {
console.log(results); // [1, 2, 3]
})
.catch(error => {
// 只要有一个 Promise 失败,立即进入 catch
console.error(error);
});

// 实际应用:并行请求多个接口
async function fetchDashboardData() {
try {
const [user, posts, stats] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/stats').then(r => r.json())
]);

return { user, posts, stats };
} catch (error) {
console.error('加载仪表盘数据失败:', error);
}
}

3.2 Promise.race

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
// 竞速,返回最先完成的结果
const racePromise = Promise.race([
fetch('/api/fast'),
fetch('/api/slow')
]);

racePromise.then(response => {
// 谁先返回就使用谁
console.log('最先完成的响应:', response);
});

// 实际应用:超时控制
function fetchWithTimeout(url, timeout = 5000) {
return Promise.race([
fetch(url),
new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('请求超时'));
}, timeout);
})
]);
}

fetchWithTimeout('/api/data')
.then(response => response.json())
.catch(error => {
if (error.message === '请求超时') {
console.log('请求超时,请重试');
}
});

3.3 Promise.allSettled

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 等待所有 Promise 完成,无论成功或失败
const promises = [
Promise.resolve(1),
Promise.reject('error'),
Promise.resolve(3)
];

Promise.allSettled(promises).then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.log('失败:', result.reason);
}
});
// 成功: 1
// 失败: error
// 成功: 3
});

3.4 Promise.any

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 返回最先成功的 Promise
const promises = [
Promise.reject('error1'),
Promise.resolve('success'),
Promise.reject('error2')
];

Promise.any(promises)
.then(result => {
console.log(result); // success
})
.catch(error => {
// 所有 Promise 都失败时
console.error('全部失败:', error);
});

四、async/await 语法糖

4.1 基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// async 函数返回 Promise
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
return data;
}

// 等价于
function fetchData() {
return fetch('/api/data')
.then(response => response.json());
}

// 使用
fetchData().then(data => console.log(data));

4.2 错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 使用 try-catch
async function handleRequest() {
try {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
return posts;
} catch (error) {
console.error('请求失败:', error);
// 可以返回默认值或重新抛出
return [];
}
}

// 或者使用 .catch()
async function handleRequest() {
const posts = await fetchPosts()
.catch(error => {
console.error('请求失败:', error);
return [];
});
return posts;
}

4.3 并发请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 错误方式:串行执行
async function getPosts() {
const post1 = await fetchPost(1); // 等待
const post2 = await fetchPost(2); // 等待
const post3 = await fetchPost(3); // 等待
return [post1, post2, post3];
}

// 正确方式:并行执行
async function getPosts() {
const [post1, post2, post3] = await Promise.all([
fetchPost(1),
fetchPost(2),
fetchPost(3)
]);
return [post1, post2, post3];
}

五、实战技巧

5.1 请求重试机制

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
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let retryCount = 0;

while (retryCount < maxRetries) {
try {
const response = await fetch(url, options);

if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}

return await response.json();
} catch (error) {
retryCount++;

if (retryCount >= maxRetries) {
throw new Error(`请求失败,已重试 ${maxRetries} 次`);
}

// 指数退避
const delay = Math.pow(2, retryCount) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}

5.2 请求缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const cache = new Map();

async function fetchWithCache(url, ttl = 60000) {
const cached = cache.get(url);

if (cached && Date.now() - cached.timestamp < ttl) {
console.log('使用缓存:', url);
return cached.data;
}

const data = await fetch(url).then(r => r.json());
cache.set(url, { data, timestamp: Date.now() });

return data;
}

5.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
36
37
38
39
40
41
42
43
44
45
46
class RequestQueue {
constructor(maxConcurrent = 5) {
this.maxConcurrent = maxConcurrent;
this.queue = [];
this.running = 0;
}

add(promiseFactory) {
return new Promise((resolve, reject) => {
this.queue.push({
promiseFactory,
resolve,
reject
});
this.process();
});
}

async process() {
if (this.running >= this.maxConcurrent || this.queue.length === 0) {
return;
}

this.running++;
const { promiseFactory, resolve, reject } = this.queue.shift();

try {
const result = await promiseFactory();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.process();
}
}
}

// 使用
const queue = new RequestQueue(3); // 最多 3 个并发

for (let i = 0; i < 10; i++) {
queue.add(() => fetch(`/api/item/${i}`).then(r => r.json()))
.then(data => console.log(`Item ${i}:`, data))
.catch(error => console.error(`Item ${i} 失败:`, error));
}

六、常见陷阱

6.1 忘记 return

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ❌ 错误
fetchUser()
.then(user => {
fetchPosts(user.id); // 忘记 return
})
.then(posts => {
console.log(posts); // undefined
});

// ✅ 正确
fetchUser()
.then(user => {
return fetchPosts(user.id);
})
.then(posts => {
console.log(posts);
});

6.2 在回调中嵌套 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ❌ 错误:回调地狱
fetchUser()
.then(user => {
fetchPosts(user.id)
.then(posts => {
fetchComments(posts[0].id)
.then(comments => {
console.log(comments);
});
});
});

// ✅ 正确:链式调用
fetchUser()
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => console.log(comments));

6.3 忽略错误

1
2
3
4
5
6
7
8
9
10
// ❌ 错误:没有错误处理
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data));

// ✅ 正确:添加错误处理
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));

七、最佳实践

7.1 总是使用 async/await

1
2
3
4
5
6
7
8
9
10
11
// 更清晰易读
async function loadData() {
try {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
return posts;
} catch (error) {
console.error(error);
return [];
}
}

7.2 合理使用 Promise.all

1
2
3
4
5
6
7
8
9
10
// 并行获取独立数据
async function loadPageData() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);

return { user, posts, comments };
}

7.3 错误处理要具体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 根据错误类型处理
async function handleRequest() {
try {
const data = await fetchData();
return data;
} catch (error) {
if (error instanceof NetworkError) {
console.log('网络错误,请检查连接');
} else if (error instanceof AuthError) {
console.log('认证失败,请重新登录');
} else {
console.error('未知错误:', error);
}
throw error; // 重新抛出或返回默认值
}
}

总结

Promise 是现代 JavaScript 异步编程的核心,掌握它对于编写高质量的代码至关重要:

  1. 理解 Promise 的三种状态和状态不可逆特性
  2. 熟练使用 then、catch 进行链式调用
  3. 掌握 Promise.all、Promise.race 等静态方法
  4. 优先使用 async/await 提高代码可读性
  5. 注意常见陷阱,遵循最佳实践

持续学习,不断进步!