什么是Promise?
Promise
是一个表示异步操作最终完成或失败的对象。它允许你更优雅地处理异步操作,避免回调地狱(Callback Hell)。
特点:
-
异步性:Promise 代表一个异步操作的最终完成或失败。
-
不可更改:一旦 Promise 的状态确定为成功(
resolved
)或失败(rejected
),它就不会再改变。 -
链式调用:可以将多个异步操作按顺序排列,形成链式调用。
Promise的基本状态
一个 Promise 对象有三种状态:
状态 | 含义 |
---|---|
Pending | 异步操作尚未完成(初始状态)。 |
Fulfilled | 异步操作成功完成。 |
Rejected | 异步操作失败。 |
创建一个Promise
Promise 通过其构造函数创建,并且需要传递一个执行器(executor
)函数。executor
函数接收两个参数:resolve
和 reject
。
// 创建一个 Promise
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true; // 假设操作成功
if (success) {
resolve("操作成功!"); // 处理成功结果
} else {
reject("操作失败!"); // 处理失败结果
}
}, 1000);
});
参数解释:
resolve
: 标志异步操作成功,将 Promise 状态变为 Fulfilled
。
reject
: 标志异步操作失败,将 Promise 状态变为 Rejected
。
resolve
和 reject
是构造器内部定义的函数,会在 Promise 被创建时自动传递给执行器。你的任务是通过执行器的逻辑调用这些函数,告诉 Promise 操作成功还是失败。
resolve()
是一个由 Promise 构造器提供的函数,用于标记异步操作成功完成的状态。它是 Promise 执行器(executor
)的一部分。当你在执行器中调用 resolve(value)
时,你会将一个值传递给它,这个值会作为 Promise 的成功结果。
const promise = new Promise((resolve) => {
// 模拟异步操作
setTimeout(() => {
const successMessage = "操作成功";
resolve(successMessage); // 将 "操作成功" 传递出去
}, 1000);
});
.then()
方法的第一个参数是一个回调函数,用于处理 resolve()
的结果。这个回调函数会接收到 resolve()
传入的参数。
promise
.then((result) => {
console.log(result); // 输出: 操作成功
})
.catch((error) => {
console.error(error);
});
resolve()
可以传递任何类型的值,包括:
-
基本数据类型(字符串、数字、布尔值等)。
-
对象或数组。
-
甚至可以传递另一个 Promise。
同理,reject函数用于传递Error错误的详细参数信息。reject()
是 Promise 中用于传递错误信息的机制。它的主要作用是告诉 Promise,异步操作失败了,并且可以携带具体的错误信息(如错误的原因、错误的详细描述等)。
使用Promise
Promise 的主要方法是 .then()
和 .catch()
,它们分别用于处理成功和失败的结果。
.then()
方法
promise
.then(
(result) => {
console.log(result); // 输出:操作成功!
},
(error) => {
console.error(error); // 如果失败,会执行这里
}
);
then
方法用于处理 executor
中的成功结果。它可以接收两个回调函数:
-
第一个参数:处理成功的回调函数。
-
第二个参数(可选):处理失败的回调函数。
.catch()
方法
catch
方法专门用于处理失败的结果。
promise
.then((result) => {
console.log(result); // 输出:操作成功!
})
.catch((error) => {
console.error(error); // 如果失败,会执行这里
});
Promise的链式调用
Promise 支持链式调用,这有助于将多个异步操作按顺序排列。
function fetchUser() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: "John Doe" });
}, 1000);
});
}
function fetchUserDetails(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ age: 30, email: "john@example.com" });
}, 1000);
});
}
// 链式调用
fetchUser()
.then((user) => {
console.log("用户信息:", user);
return fetchUserDetails(user.id); // 返回一个新的 Promise
})
.then((details) => {
console.log("用户详细信息:", details);
})
.catch((error) => {
console.error("错误:", error);
});
什么是 Axios?
Axios 是一个基于 Promise 的 HTTP 库,用于发送网络请求。它可以在浏览器和 Node.js 环境中使用,支持各种 HTTP 方法(如 GET
, POST
, PUT
, DELETE
等)。它的主要特点包括:
-
简单易用:API 设计直观,易于上手。
-
支持 Promise:与现代 JavaScript 无缝集成。
-
拦截器:可以在请求发送前和响应接收后自定义处理逻辑。
-
取消请求:可以取消正在执行的请求。
-
错误处理:提供统一的错误处理机制。
-
支持请求和响应的序列化。
什么是 async/await
async/await
是 JavaScript 提供的用于处理异步操作的语法糖,它简化了异步代码的编写和阅读。它的本质是基于 Promise
的异步操作封装,使得异步代码可以像同步代码一样书写,避免了 .then()
链的嵌套问题。
核心特点:
-
async
:修饰一个函数,表明该函数包含异步操作,会自动返回一个Promise
。 -
await
:用于等待一个Promise
完成,只能在async
函数内部使用。
// async/await
async function asyncFunction() {
try {
const result = await PromiseReturningFunction();
return result;
} catch (error) {
console.error(error);
}
}
// 等同于以下 Promise 写法
function promiseFunction() {
return PromiseReturningFunction()
.then(result => {
return result;
})
.catch(error => {
console.error(error);
});
}
更方便链式调用和组合
async/await
允许将异步操作写成一个序列,而不用像 then()
那样通过链式调用来处理依赖关系。
// 使用 then()
fetchData1()
.then(data1 => {
return fetchData2(data1);
})
.then(data2 => {
return fetchData3(data2);
})
.then(data3 => {
console.log(data3);
});
// 使用 async/await
async function handleData() {
const data1 = await fetchData1();
const data2 = await fetchData2(data1);
const data3 = await fetchData3(data2);
console.log(data3);
}
业务场景和函数类型建议使用 async/await
的情况
需要处理多个异步操作
当你需要按顺序执行多个异步操作,并且每个操作的结果可能会影响下一个操作时,async/await
是最佳选择。
async function fetchData() {
try {
const user = await fetchUserById(1);
const posts = await fetchPostsByUserId(user.id);
console.log('User:', user);
console.log('Posts:', posts);
} catch (error) {
console.error('Error:', error);
}
}
需要分解异步流程的复杂逻辑
对于复杂的异步流程,async/await
可以将逻辑分解为多个函数,使代码更模块化。
async function downloadAndProcessFile(url) {
const filePath = await downloadFile(url);
const fileContent = await readFile(filePath);
const processedData = processFile(fileContent);
return processedData;
}
创建 Axios 实例
Axios项目结构
src/
├── utils/ # 工具模块
│ ├── axios.js # 封装 Axios 实例
├── api/ # API 模块
│ ├── auth.js # 用户认证 API
│ ├── user.js # 用户相关 API
├── store/ # 状态管理
│ ├── auth.js # 用户认证状态管理
│ ├── user.js # 用户信息状态管理
├── components/ # UI 组件
├── App.vue # 根组件
在前端项目中,Axios 相关的文件通常位于以下位置:
-
项目根目录:
-
package.json
:通过npm
或yarn
添加 Axios 依赖后的配置文件。 -
node_modules
:包含 Axios 的安装包。
-
-
工具模块:
-
src/utils/axios.js
或src/utils/request.js
:用于封装 Axios 实例,统一配置请求和响应拦截器。
-
-
API 模块:
-
src/api/auth.js
:封装用户认证相关的 API。 -
src/api/user.js
:封装用户相关的 API(如获取用户信息)。 -
src/api/service.js
:封装服务相关的 API(如获取服务列表)。
-
-
Vuex 或 Pinia 模块:
-
如果项目使用状态管理库(如 Vuex 或 Pinia),API 调用逻辑可能会出现在这些模块中。
-
使用Vue3+Pinia项目进行演示
(src/api/axios.js
)
// src/utils/axios.js (修改后)
import axios from 'axios';
const http = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
timeout: 10000,
headers: { 'Content-Type': 'application/json' }
});
// 请求拦截器优化
http.interceptors.request.use(config => {
const accessToken = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken');
// 设置 accessToken 到请求头
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
// 如果是刷新 Token 的请求,将 refreshToken 放到自定义请求头
if (config.url === '/auth/refresh-token' && refreshToken) {
config.headers['X-Refresh-Token'] = refreshToken;
}
return config;
});
// 响应拦截器优化
http.interceptors.response.use(
response => response.data, // 自动解构 data
async error => {
const originalRequest = error.config;
// 检查是否为 401 错误且未重试过
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true; // 标记请求已重试
try {
// 使用独立实例避免拦截器循环
const refreshClient = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
headers: {
'X-Refresh-Token': localStorage.getItem('refreshToken') // 通过自定义请求头传递 refreshToken
}
});
// 调用刷新 Token 接口
const { data } = await refreshClient.post('/auth/refresh-token');
// 更新本地存储的 Token
localStorage.setItem('accessToken', data.accessToken);
localStorage.setItem('refreshToken', data.refreshToken);
// 更新原始请求的 Authorization 头
originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;
// 重新发起原始请求
return http(originalRequest);
} catch (refreshError) {
// 刷新 Token 失败,清除本地存储并跳转到登录页
localStorage.clear();
window.location.href = '/login'; // 使用路由跳转而非页面刷新
return Promise.reject(refreshError);
}
}
// 其他错误直接抛出
return Promise.reject(error);
}
);
export default http;
接口服务层封装
// src/api/auth.js (保持良好)
import http from '@/utils/axios';
export const login = credentials => http.post('/auth/login', credentials);
export const register = userData => http.post('/auth/register', userData);
export const refreshToken = () => http.post('/auth/refresh-token'); // 参数通过拦截器自动携带
登录使用双 Token 策略保证安全性
双 Token 策略通常包括:
-
Access Token:短时间有效,用于用户认证。
-
Refresh Token:长时间有效,用于刷新 Access Token。
Pinia Store
// src/store/auth.js (组合式 API 版本)
import { defineStore } from 'pinia';
import { login, register } from '@/api/auth';
import { useRouter } from 'vue-router';
export const useAuthStore = defineStore('auth', () => {
const router = useRouter();
// State
const isAuthenticated = ref(!!localStorage.getItem('accessToken'));
const accessToken = ref(localStorage.getItem('accessToken') || null);
const refreshToken = ref(localStorage.getItem('refreshToken') || null);
// Actions
const handleTokenUpdate = (newAccessToken, newRefreshToken) => {
accessToken.value = newAccessToken;
refreshToken.value = newRefreshToken;
localStorage.setItem('accessToken', newAccessToken);
localStorage.setItem('refreshToken', newRefreshToken);
isAuthenticated.value = true;
};
const loginAction = async (credentials) => {
try {
const data = await login(credentials);
handleTokenUpdate(data.accessToken, data.refreshToken);
} catch (error) {
throw new Error('登录失败: ' + error.message); // 抛出错误供组件处理
}
};
const registerAction = async (userData) => {
try {
await register(userData);
await router.push('/login'); // 正确使用路由
} catch (error) {
throw new Error('注册失败: ' + error.message);
}
};
const logout = () => {
accessToken.value = null;
refreshToken.value = null;
isAuthenticated.value = false;
localStorage.clear();
router.push('/login');
};
return {
isAuthenticated,
accessToken,
refreshToken,
login: loginAction,
register: registerAction,
logout
};
});
组件中调用
<!-- Login.vue -->
<template>
<!-- 保持原有模板内容,v-model 绑定保持不变 -->
</template>
<script setup>
import { ref } from 'vue'
import { useAuthStore } from '@/store/auth'
const username = ref('')
const password = ref('')
const handleLogin = () => {
const authStore = useAuthStore()
authStore.login({
username: username.value,
password: password.value
})
}
</script>
<!-- Register.vue -->
<template>
<!-- 保持原有模板内容,v-model 绑定保持不变 -->
</template>
<script setup>
import { ref } from 'vue'
import { useAuthStore } from '@/store/auth'
const username = ref('')
const email = ref('')
const password = ref('')
const handleRegister = () => {
const authStore = useAuthStore()
authStore.register({
username: username.value,
email: email.value,
password: password.value
})
}
</script>