概念
Promise是JavaScript的ES6+规范中的处理异步操作的内置对象。
-
状态:Promise有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
-
不可逆性:状态一旦改变(从pending到fulfilled或rejected),就不会再变
-
链式调用:通过
.then()、.catch()、.finally()方法实现链式调用
优点
-
链式调用:扁平化异步代码结构,避免回调地狱
-
错误冒泡:错误可以统一在最后处理
-
更好的可读性:代码逻辑更清晰
基本用法
1. 创建Promise
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('操作成功!');
} else {
reject(new Error('操作失败!'));
}
}, 1000);
});
2. 使用Promise
promise
.then(result => {
console.log(result); // 成功时执行
})
.catch(error => {
console.error(error.message); // 失败时执行
})
.finally(() => {
console.log('无论成功失败都会执行');
});
3. Promise的静态方法
Promise.resolve() / Promise.reject()
快速创建已确定状态的Promise:
// 立即成功的Promise
Promise.resolve('立即成功').then(console.log);
// 立即失败的Promise
Promise.reject(new Error('立即失败')).catch(console.error);
Promise.all()
等待所有Promise完成(全部成功或有一个失败):
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(results => {
console.log(results); // [1, 2, 3]
})
.catch(error => {
console.error(error);
});
Promise.allSettled()
等待所有Promise完成(无论成功或失败):
const promises = [
Promise.resolve('成功1'),
Promise.reject('失败'),
Promise.resolve('成功2')
];
Promise.allSettled(promises)
.then(results => {
results.forEach(result => {
console.log(result.status); // "fulfilled" 或 "rejected"
});
});
Promise.race()
返回最先完成的Promise(无论成功或失败):
const promise1 = new Promise(resolve => setTimeout(() => resolve('快'), 100));
const promise2 = new Promise(resolve => setTimeout(() => resolve('慢'), 200));
Promise.race([promise1, promise2])
.then(result => console.log(result)); // "快"
Promise.any()
返回第一个成功的Promise(ES2021新增):
const promise1 = Promise.reject('失败1');
const promise2 = Promise.resolve('成功2');
const promise3 = Promise.reject('失败3');
Promise.any([promise1, promise2, promise3])
.then(result => console.log(result)) // "成功2"
.catch(errors => console.log(errors));
链式调用
Promise的核心优势在于链式调用,可以避免回调地狱:
function getUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id, name: '张三' });
}, 1000);
});
}
function getPosts(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(['文章1', '文章2']);
}, 1000);
});
}
function getComments(postId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(['评论1', '评论2']);
}, 1000);
});
}
// 链式调用示例
getUser(1)
.then(user => {
console.log('用户:', user);
return getPosts(user.id); // 返回新的Promise
})
.then(posts => {
console.log('文章:', posts);
return getComments(posts[0]);
})
.then(comments => {
console.log('评论:', comments);
})
.catch(error => {
console.error('出错:', error);
});
关键点:
- 每个
.then()可以返回一个值或新的Promise - 返回的值会被包装成Promise.resolve()
- 错误会沿着链向下传递,直到被
.catch()捕获
错误处理
1. 使用.catch()统一处理错误
someAsyncOperation()
.then(result => {
// 处理成功结果
})
.catch(error => {
// 统一处理所有错误
console.error('操作失败:', error);
});
2. 在链式调用中处理错误
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.catch(error => {
// 处理前面任何步骤的错误
console.error(error);
});
3. 避免在.then()中抛出错误
// 返回rejected Promise
promise.then(result => {
if (!result) {
// throw new Error('结果为空'); // 不要在then中抛出错误
return Promise.reject(new Error('结果为空'));
}
return result;
});
异步任务类型
Promise的回调属于微任务,会在当前事件循环的微任务阶段执行,优先级高于宏任务(setTimeout等):
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// 输出顺序:1 → 4 → 3 → 2
未处理的reject
未处理的reject可能导致内存泄漏或程序崩溃:
// 添加错误处理,即.catch
someAsyncOperation()
.then(result => console.log(result))
.catch(error => console.error(error));
async/await
async/await是ES2017引入的语法糖,基于Promise,让异步代码看起来像同步代码:
- 在async函数中使用try-catch处理错误
- await只能用在async函数中
- async函数总是返回Promise
// 使用Promise
function fetchData() {
return fetch('/api/data')
.then(response => response.json())
.then(data => data);
}
// 使用async/await
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
return data;
}
应用
1、并发请求优化
// 串行请求(慢)
async function serialRequests() {
const user = await getUser(1);
const posts = await getPosts(user.id);
const comments = await getComments(posts[0]);
return comments;
}
// 并发请求(快)
async function parallelRequests() {
const [user, posts, comments] = await Promise.all([
getUser(1),
getPosts(1),
getComments(1)
]);
return { user, posts, comments };
}
2、超时控制
function withTimeout(promise, timeoutMs) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时')), timeoutMs);
});
return Promise.race([promise, timeoutPromise]);
}
// 使用
withTimeout(fetch('/api/data'), 5000)
.then(response => response.json())
.catch(error => {
if (error.message === '请求超时') {
console.log('超时了');
} else {
console.error('其他错误:', error);
}
});
3、重试机制
function retry(fn, retries = 3, delay = 1000) {
return new Promise((resolve, reject) => {
const attempt = (attempts) => {
fn()
.then(resolve)
.catch(error => {
if (attempts <= 0) {
reject(error);
} else {
setTimeout(() => attempt(attempts - 1), delay);
}
});
};
attempt(retries);
});
}
// 使用
retry(() => fetch('/api/data'), 3, 1000)
.then(response => response.json())
.catch(error => console.error('重试3次后失败:', error));