一、什么是Promise
Promise 是 ES6 引入的异步编程解决方案,用于处理异步操作的结果。它代表了一个未来才会完成(或失败)的操作及其结果值。核心概念:Promise 是一个对象,表示异步操作的最终完成(或失败)从"pending"(进行中)变为"fulfilled"(已成功)或"rejected"(已失败)一旦状态改变,就不会再变(不可逆)
核心概念
- Promise是一个对象,表示异步操作的最终完成(或失败)
- 从"pending"(进行中) 变为 "fulfilled"(已成功) 或 "rejected"(已失败)
- 一旦状态改变,就不会再变(不可逆)
二、Promise的三种状态
| 状态 | 说明 | 触发方式 |
|---|---|---|
| Pending | 初始状态,未决 | 创建 Promise 时 |
| Fulfilled | 操作成功完成 | 调用 resolve(value) |
| Rejected | 操作失败 | 调用 reject(reason) |
特点
- 状态一旦改变就不逆
- 只能从 Pending -> Fulfilled 或 Pending -> Rejected
- 改变后会立即触发相应的回调
- 不管执行结果如何,一定会有一个最终状态。
三、Promise的核心方法
| 名称 | 说明 | 触发方式 |
|---|---|---|
| .then() | 处理成功和失败 | promise.then() |
| .catch() | 捕获错误 | promise.catch() |
| .finally() | 最终处理(不管状态如何,都会执行) | promise.finally() |
四、基本语法
javascript
// new Promise() 构造函数中有一个回调函数,回调函数中有2个参数分别为 resolve reject函数,根据Promise的状态,执行对应的回调。
const promise = new Promise((resolve, reject) => {
if(false){
resolve('Fulfilled') // 将状态改为 Fulfilled
} else {
reject('Rejected') // 将状态改为 Rejected
}
})
promise
.then(result => {
// 处理成功的结果
console.log('成功:' , result)
// 打印结果 'Fulfilled'
})
.catch(error => {
console.log('失败:', error)
// 打印结果 'Rejected'
})
.finally(() => {
// 无论成功、失败都会执行,和axios库中的接口调用一样,都会执行的。
console.log('不管状态如何,都会执行')
})
五、完整示例
- 模拟异步请求
javascript
// 创建一个模拟异步请求的 Promise
function fetchData(id){
return new Promise((resolve, reject) => {
setTimeout(() => {
if(id > 0) {
resolve({id: id, name: '数据' + id })
} else {
reject("无效的id")
}
}, 1000)
})
}
fetchData(1)
.then(data => {
console.log('数据获取成功', data)
// 输出: 获取成功: { id: 1, name: '数据1' }
})
.catch(error => {
console.log('获取失败', error)
})
- 读取文件(Node.js)
javascript
const fs = require('fs')
function readFileAsync(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (err, data) => {
if(err) {
reject(err)
} else {
resolve(data)
}
})
})
}
readFileAsync
.then(data) {
console.log('文件内容:', data)
}
.catch(err) {
console.log('读取失败:', err )
}
- AJAX 请求封装
javascript
// 使用Promise封装ajax请求的好处:1.可以链式调用。2.避免回调地狱
function ajax(url, method = 'GET', data = null){
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText)); }
else {
reject(new Error(`HTTP Error: ${xhr.status}`));
}
};
xhr.onerror = () => {
reject(new Error('Network Error'));
};
xhr.send(data ? JSON.stringify(data) : null); });
}
// 使用
ajax('https://api.example.com/users')
.then(users => { console.log('用户列表:', users); })
.catch(error => { console.error('请求失败:', error); });
六、Promise 的核心方法示例
- then() 处理成功和失败
javascript
promise.then(onFulfilled, onRejected)
// 等价于 promise
.then(onFulfilled)
.catch(onRejected)
示例:
javascript
fetchData(1)
.then(
data => console.log('成功:', data), //onFulfilled
error => console.log('失败:', error) // onRejected
)
链式调用:
javascript
fetchData(1)
.then(data => {
console.log('第一步:', data)
return fetchData(2)
})
.then(data => {
console.log('第二步:', data)
return fetchData(3)
})
.then(data => {
console.log('第三步:', data)
})
.catch(error => {
console.log('任何一步出错:', error)
})
输出:
javascript
第一步: { id: 1, name: '数据1' }
第二步: { id: 2, name: '数据2' }
第三步: { id: 3, name: '数据3' }
- catch() 捕获错误
javascript
promise.catch(onRejected)
// 等价于
promise.then(null, onRejected)
示例:
javascript
fetchData(-1)
.then(data => { console.log('不会执行'); })
.catch(error => { console.error('捕获到错误:', error.message); // 输出: 捕获到错误: 无效的 ID });
链式中的错误捕获:
javascript
fetchData(1)
.then(data => {
console.log('第一步成功');
return fetchData(-1); // 这里会失败
})
.then(data => { console.log('不会执行'); })
.catch(error => { console.error('捕获错误:', error.message); // 输出: 捕获错误: 无效的 ID });
- finally() 最终执行
javascript
promise.finally(onFinally)
特点:
- 无论成功或失败都会执行
- 不接受任何参数
- 常用于清理工作
示例:
javascript
let isLoading = true;
fetchData(1)
.then(data => { console.log('数据:', data); })
.catch(error => { console.error('错误:', error); })
.finally(() => { isLoading = false; console.log('加载结束'); // 总是执行 });
- 链式调用原理
关键规则:
- .then()总是返回一个新的 promise
- 返回值决定下一个 .then() 的参数
- 抛出异常会跳到 .catch()
示例解析:
javascript
Promise.resolve(1)
.then(data => {
return data + 1 // 返回2
})
.then(data => {
console.log(data) // 输出 2
return data * 2 // 返回 4
})
.then(data => {
console.log(data) // 输出4
throw new Error('出错了') //抛出异常
})
.catch(error => {
console.log('error', error) //输出:出错了
return 0 // 重置 data值
})
.then(data => {
console.log('重置后的值:', data) // 输出0
})
七、Promise 的静态方法
- Promise.resolve() 创建已解决的 Promise
javascript
// 方式一 传入值
cosnt res = Promise.resolve(20)
res.then(data => {
console.log(data) // 输出20
})
// 方式二 传入Promise(原样返回)
const p2 = Promise.resolve(Promise.resolve(20))
console.log(p2 === p1) // false
2 Promise.reject() 创建已拒绝的 Promise
javascript
const p = Promise.reject(new Error('出错了'))
p.catch(error => {
console.error(error)
}
3 Promise.all() 并行执行多个 Promise
javascript
Promise.all([p1, p2])
.then( results => {
// results是所有请求结果的数组,按照请求顺序返回的
})
.catch( error => {
// 执行错误
})
示例:
javascript
const p1 = fetchData(1)
const p2 = fetchData(2)
Promise.all([p1, p2]).then(results => {
console.log(results)
// 输出数据
// { id: 1, name: '数据1'}
// { id: 2, name: '数据2'}
}).catch( error => {
console.log('error', )
})
特点:
- 所有Promise都成功才算成功
- 任何一个失败就立即失败(短路)
- 并行执行,总时间取决于最慢的那个
4 Promise.race() 竞速
javascript
Promise.race([p1, p2])
.then(first => {
// 第一个完成的 Promise 的结果
})
示例:超时控制
javascript
function timeOut(ms){
return new Promise((_, reject) => {
setTimeout(() => reject(new Error(`超时${ms}`)), ms)
})
}
Promise.race([
fetchData(1), // 正常请求
timeOut(500) // 超时请求
]).then(data => {
console.log(data)
}).catch(error => {
console.error('错误', error.message)
})
5 Promise.allSettled() 等待所有完成(ES2020)
javascript
Promise.allSettled([p1, p2, p3])
.then(results => {
// results 包含所有的 Promise 的状态和结果
})
示例:
javascript
const p1 = Promise.resolve(1)
const p2 = Promise.reject(2)
const p3 = Promise.resolve(3)
Promise.allSettled([p1, p2, p3])
.then( results => {
console.log(results)
})
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 2 },
// { status: 'fulfilled', value: 3 }
// ]
// 这个示例可以 使用 Promise.all 调用一下,看看输出的内容是什么?
与 Promise.all() 的区别:
- Promise.all: 一个失败就全部失败
- Promise.allSettled: 等待所有完成,不管成功失败 6 Promise.any() 第一个完成的(ES2021)
javascript
Promise.any([p1, p2])
.then(first => {
// 第一个成功的 Promise 的结果
})
.catch(error => {
// 所有的失败和结果返回慢的数据 都会在失败里面
}
示例:
javascript
// 从多个镜像源下载,哪个快用哪个
Promise.any([
fetchFromMirror1(),
fetchFromMirror2(),
fetchFromMirror3()
])
.then(data => {
console.log('获取到数据:', data);
})
.catch(error => {
console.error('所有镜像都失败了:', error);
});
八、常见错误和陷阱
- 错误1: 忘记return
javascript
// ❌ 错误写法
fetchData(1)
.then(data => {
console.log(data);
fetchData(2); // 没有 return,下一个 .then() 收到 undefined
})
.then(data => {
console.log(data); // undefined
});
// ✅ 正确写法
fetchData(1)
.then(data => {
console.log(data);
return fetchData(2); // 返回 Promise
})
.then(data => {
console.log(data); // { id: 2, name: '数据2' }
});
- 错误2: 嵌套 Promise (回调地狱)
javascript
// ❌ 错误写法(嵌套)
fetchData(1)
.then(data1 => {
fetchData(2)
.then(data2 => {
fetchData(3)
.then(data3 => {
console.log(data1, data2, data3);
});
});
});
// ✅ 正确写法(链式)
fetchData(1)
.then(data1 => {
return fetchData(2).
then(data2 => {
return fetchData(3).then(data3 => {
return { data1, data2, data3 };
});
});
})
.then(({ data1, data2, data3 }) => {
console.log(data1, data2, data3);
});
// ✅ 更好的写法(使用 async/await)
async function getAllData() {
const data1 = await fetchData(1);
const data2 = await fetchData(2);
const data3 = await fetchData(3);
console.log(data1, data2, data3);
}
- 错误3: 错误没有被捕获
javascript
// ❌ 错误写法
fetchData(-1)
.then(data => {
console.log(data);
});
// 错误被吞掉,没有任何提示
// ✅ 正确写法
fetchData(-1)
.then(data => {
console.log(data);
})
.catch(error => {
console.error('捕获到错误:', error);
});
- 错误4: 在循环中使用 Promise
javascript
// ❌ 串行执行(慢)
for (let i = 1; i <= 3; i++) {
fetchData(i).then(data => console.log(data));
}
// ✅ 并行执行(快)
const promises = [];
for (let i = 1; i <= 3; i++) {
promises.push(fetchData(i));
}
Promise.all(promises).then(results => {
console.log(results);
});
// ✅ 或使用 map
Promise.all([1, 2, 3].map(id => fetchData(id)))
.then(results => {
console.log(results);
});
九、Promise vs 回调函数
- 回调地狱问题
javascript
getUser(userId, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
getAuthor(comments[0].authorId, (author) => {
console.log(author);
});
});
});
});
- Promise 解决方案
javascript
// Promise 链式调用(扁平化)
getUser(userId)
.then(user => getPosts(user.id))
.then(posts => getComments(posts[0].id))
.then(comments => getAuthor(comments[0].authorId))
.then(author => console.log(author))
.catch(error => console.error(error));
- async/await 更优雅
javascript
// async/await(同步写法)
async function getAuthorInfo(userId) {
try {
const user = await getUser(userId);
const posts = await getPosts(user.id);
const comments = await getComments(posts[0].id);
const author = await getAuthor(comments[0].authorId);
console.log(author);
} catch (error) {
console.error(error);
}
}
十、实战应用
- 场景1: 并发请求优化
javascript
// 需求:同时获取用户信息和商品列表
// ❌ 串行(慢)
async function getData() {
const user = await fetchUser(); // 1s
const products = await fetchProducts(); // 1s
// 总共 2s
return { user, products };
}
// ✅ 并行(快)
async function getData() {
const [user, products] = await Promise.all([
fetchUser(), // 1s
fetchProducts() // 1s
]);
// 总共 1s(并行执行)
return { user, products };
}
- 场景2: 重试机制
javascript
function retry(fn, retries = 3, delay = 1000) {
return new Promise((resolve, reject) => {
function attempt(n) {
fn()
.then(resolve)
.catch(error => {
if (n <= 1) {
reject(error);
} else {
setTimeout(() => {
console.log(`重试 (${retries - n + 1}/${retries})...`);
attempt(n - 1);
}, delay);
}
});
}
attempt(retries);
});
}
// 使用
retry(() => fetchData(-1), 3, 1000)
.then(data => console.log('成功:', data))
.catch(error => console.error('最终失败:', error));
- 场景3: 请求队列(限制并发数)
javascript
class RequestQueue {
constructor(maxConcurrent = 5) {
this.maxConcurrent = maxConcurrent;
this.current = 0;
this.queue = [];
}
add(requestFn) {
return new Promise((resolve, reject) => {
this.queue.push({ requestFn, resolve, reject });
this.process();
});
}
process() {
if (this.current >= this.maxConcurrent || this.queue.length === 0) {
return;
}
this.current++;
const { requestFn, resolve, reject } = this.queue.shift();
requestFn()
.then(result => {
resolve(result);
this.current--;
this.process();
})
.catch(error => {
reject(error);
this.current--;
this.process();
});
}
}
// 使用
const queue = new RequestQueue(3); // 最多同时 3 个请求
[1, 2, 3, 4, 5].forEach(id => {
queue.add(() => fetchData(id))
.then(data => console.log(`数据${id}:`, data));
});
十一、总结
Promise 的核心优势:
| 特性 | 说明 |
|---|---|
| 解决回调地狱 | 链式调用,代码更扁平 |
| 统一错误处理 | .catch() 集中捕获错误 |
| 状态不可变 | 一旦决议,状态不再改变 |
| 组合性强 | Promise.all、race 等静态方法 |
| 标准化 | ES6标准、浏览器原生支持 |
最佳实践:
- 终添加 .catch() 处理错误
- 用 Promise.all() 并行执行独立请求
- 避免嵌套 Promise,使用链式调用
- .then() 中记得 return
- 使用 async/await 简化复杂逻辑