乐闻世界logo
搜索文章和话题

面试题手册

什么是 MobX,它的工作原理是什么?

MobX 是一个基于信号的、经过实战测试的状态管理库,通过透明地应用函数式响应式编程(FRP)使状态管理变得简单和可扩展。它通过观察者模式自动追踪状态变化,当可观察状态发生变化时,MobX 会自动更新所有依赖该状态的派生值和反应。核心概念1. Observable(可观察状态)使用 observable 或 makeObservable 将普通 JavaScript 对象、数组、Map 等转换为可观察对象。可观察状态的变化会被 MobX 自动追踪。import { observable } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all';}2. Action(动作)动作是修改状态的唯一方式,使用 action 装饰器或 runInAction 函数包装状态修改逻辑。这确保了状态变化是可追踪和可预测的。import { action } from 'mobx';class TodoStore { @action addTodo(text) { this.todos.push({ text, completed: false }); }}3. Computed(计算值)计算值是基于可观察状态自动更新的派生值,类似于 Vue 的 computed 属性。只有当依赖的可观察状态发生变化时才会重新计算。import { computed } from 'mobx';class TodoStore { @computed get activeTodos() { return this.todos.filter(todo => !todo.completed); }}4. Reaction(反应)反应是当可观察状态变化时自动执行的副作用,类似于 React 的 useEffect。常用的反应类型包括 autorun、reaction 和 when。import { autorun } from 'mobx';autorun(() => { console.log('当前待办事项数量:', this.todos.length);});工作原理MobX 的工作流程:追踪阶段:当反应或计算值读取可观察状态时,MobX 会建立依赖关系变化阶段:通过 action 修改可观察状态通知阶段:MobX 自动通知所有依赖该状态的反应和计算值更新阶段:反应和计算值自动重新执行或重新计算与 Redux 的区别| 特性 | MobX | Redux ||------|------|-------|| 状态管理 | 自动追踪,无需手动订阅 | 需要手动订阅和 dispatch || 代码量 | 较少,更简洁 | 较多,需要定义 actions、reducers || 学习曲线 | 较平缓 | 较陡峭 || 状态结构 | 可以嵌套 | 推荐扁平化 || 调试工具 | MobX DevTools | Redux DevTools |最佳实践始终使用 action 修改状态:确保状态变化可追踪合理使用 computed:避免重复计算,提高性能避免过度使用 observable:只对需要追踪的状态使用使用 makeAutoObservable:简化装饰器配置分离业务逻辑和 UI:将状态管理逻辑集中在 store 中适用场景MobX 适用于:中大型 React 应用需要复杂状态管理的项目团队希望快速开发的项目状态结构复杂且嵌套的场景不适用于:非常简单的应用需要严格时间旅行调试的场景团队偏好函数式编程范式
阅读 0·2月22日 14:08

MobX 和 Redux 的区别是什么,如何选择?

MobX 和 Redux 都是流行的状态管理库,但它们的设计理念和使用方式有很大的不同。选择哪一个取决于项目需求、团队偏好和具体场景。核心设计理念MobX基于观察者模式:自动追踪状态变化,无需手动订阅命令式编程:直接修改状态,更符合直觉透明响应式:状态变化自动触发更新灵活性高:不强制特定的代码结构Redux基于函数式编程:使用纯函数处理状态变化声明式编程:通过 dispatch action 来修改状态单向数据流:Action → Reducer → Store → View规范性高:强制特定的代码结构代码对比MobX 示例import { observable, action, computed, makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } @computed get filteredTodos() { switch (this.filter) { case 'completed': return this.todos.filter(todo => todo.completed); case 'active': return this.todos.filter(todo => !todo.completed); default: return this.todos; } } @action addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } @action toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } @action setFilter(filter) { this.filter = filter; }}const store = new TodoStore();// 使用store.addTodo('Learn MobX');store.toggleTodo(store.todos[0].id);Redux 示例import { createStore } from 'redux';// Action Typesconst ADD_TODO = 'ADD_TODO';const TOGGLE_TODO = 'TOGGLE_TODO';const SET_FILTER = 'SET_FILTER';// Action Creatorsconst addTodo = (text) => ({ type: ADD_TODO, payload: { id: Date.now(), text, completed: false } });const toggleTodo = (id) => ({ type: TOGGLE_TODO, payload: id });const setFilter = (filter) => ({ type: SET_FILTER, payload: filter });// Reducerconst initialState = { todos: [], filter: 'all'};function todoReducer(state = initialState, action) { switch (action.type) { case ADD_TODO: return { ...state, todos: [...state.todos, action.payload] }; case TOGGLE_TODO: return { ...state, todos: state.todos.map(todo => todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo ) }; case SET_FILTER: return { ...state, filter: action.payload }; default: return state; }}const store = createStore(todoReducer);// Selectorconst selectFilteredTodos = (state) => { switch (state.filter) { case 'completed': return state.todos.filter(todo => todo.completed); case 'active': return state.todos.filter(todo => !todo.completed); default: return state.todos; }};// 使用store.dispatch(addTodo('Learn Redux'));store.dispatch(toggleTodo(store.getState().todos[0].id));详细对比| 特性 | MobX | Redux ||------|------|-------|| 编程范式 | 命令式、面向对象 | 函数式、声明式 || 状态追踪 | 自动追踪 | 手动订阅 || 状态修改 | 直接修改 | 通过 action || 代码量 | 较少 | 较多 || 学习曲线 | 平缓 | 陡峭 || 灵活性 | 高 | 低 || 规范性 | 低 | 高 || 调试工具 | MobX DevTools | Redux DevTools || 时间旅行 | 有限支持 | 完整支持 || 中间件 | 不需要 | 丰富(redux-thunk、redux-saga 等) || 性能 | 自动优化 | 需要手动优化 || 状态结构 | 可以嵌套 | 推荐扁平化 || 类型支持 | 良好 | 需要额外配置 |使用场景适合使用 MobX 的场景快速开发:需要快速原型开发或小型项目复杂状态结构:状态结构复杂且嵌套团队经验:团队更熟悉面向对象编程灵活性优先:需要更多的代码灵活性学习成本:希望降低学习成本// MobX 适合复杂嵌套状态class UserStore { @observable user = { profile: { name: '', email: '', address: { city: '', country: '' } }, preferences: { theme: 'light', language: 'en' } }; @action updateCity(city) { this.user.profile.address.city = city; // 直接修改嵌套属性 }}适合使用 Redux 的场景大型项目:需要严格规范的大型项目团队协作:多人协作,需要统一的代码规范时间旅行:需要完整的时间旅行调试功能中间件需求:需要使用丰富的中间件生态函数式编程:团队偏好函数式编程范式// Redux 适合扁平化状态const initialState = { users: { byId: {}, allIds: [] }, profiles: { byId: {}, allIds: [] }, addresses: { byId: {}, allIds: [] }};// 通过 reducer 处理状态变化function reducer(state = initialState, action) { switch (action.type) { case UPDATE_CITY: return { ...state, addresses: { ...state.addresses, byId: { ...state.addresses.byId, [action.payload.id]: { ...state.addresses.byId[action.payload.id], city: action.payload.city } } } }; default: return state; }}性能对比MobX 性能优势自动优化:自动追踪依赖,只更新必要的组件批量更新:action 内部的状态变化会被批量处理懒计算:computed 值只在被访问时才计算// MobX 自动优化class Store { @observable items = []; @computed get expensiveValue() { console.log('Computing expensive value'); return this.items.reduce((sum, item) => sum + item.value, 0); }}// 只有在访问 expensiveValue 时才会计算console.log(store.expensiveValue); // 计算一次console.log(store.expensiveValue); // 使用缓存,不计算Redux 性能挑战手动优化:需要使用 reselect、memo 等工具优化全量比较:每次 dispatch 都会比较整个状态树需要手动订阅:需要手动选择需要的数据// Redux 需要手动优化import { createSelector } from 'reselect';const selectItems = (state) => state.items;const selectExpensiveValue = createSelector( [selectItems], (items) => { console.log('Computing expensive value'); return items.reduce((sum, item) => sum + item.value, 0); });// 需要手动选择数据const value = selectExpensiveValue(store.getState());调试对比MobX 调试// 使用 MobX DevToolsimport { makeObservable, observable, action } from 'mobx';class Store { @observable count = 0; constructor() { makeObservable(this); } @action increment() { this.count++; }}// 在浏览器中查看状态变化Redux 调试// 使用 Redux DevToolsimport { createStore } from 'redux';const store = createStore( reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());// 可以查看完整的 action 历史、状态变化和时间旅行迁移建议从 MobX 迁移到 Redux重构状态结构:将嵌套状态扁平化创建 action types:定义所有可能的 action编写 reducers:将状态修改逻辑移到 reducers使用中间件:根据需要添加中间件更新组件:使用 useSelector 和 useDispatch从 Redux 迁移到 MobX创建 stores:将 reducer 逻辑转换为 stores使用 observable:将状态转换为 observable添加 actions:将 action creators 转换为 actions更新组件:使用 observer 或 useObserver简化代码:移除不必要的样板代码总结选择 MobX 如果:需要快速开发状态结构复杂且嵌套团队更熟悉面向对象编程希望降低学习成本需要更多的代码灵活性选择 Redux 如果:项目规模较大需要严格的代码规范需要完整的时间旅行调试需要丰富的中间件生态团队偏好函数式编程两者都是优秀的状态管理库,选择哪一个应该基于项目需求和团队情况。在实际项目中,也可以根据不同模块的特点混合使用。
阅读 0·2月22日 14:08

MobX 6 相比 MobX 4/5 有哪些重要变化?

MobX 6 是 MobX 的最新主要版本,相比 MobX 4/5 有许多重要的变化和改进。了解这些变化对于升级和维护项目非常重要。主要变化1. 移除装饰器支持MobX 6 默认不再支持装饰器语法,推荐使用 makeObservable 或 makeAutoObservable。MobX 4/5(装饰器语法):import { observable, action, computed } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all'; @computed get completedTodos() { return this.todos.filter(todo => todo.completed); } @action addTodo(text) { this.todos.push({ text, completed: false }); }}MobX 6(推荐方式):import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}2. makeObservable 和 makeAutoObservableMobX 6 引入了两个新的 API 来替代装饰器:makeObservable需要显式指定每个属性的类型。import { makeObservable, observable, action, computed } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeObservable(this, { todos: observable, filter: observable, completedTodos: computed, addTodo: action }); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}makeAutoObservable(推荐)自动推断属性类型,更简洁。import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}3. 移除 configureMobX 6 移除了 configure API,不再需要全局配置。MobX 4/5:import { configure } from 'mobx';configure({ enforceActions: 'always', useProxies: 'ifavailable'});MobX 6:// 不再需要 configure,默认行为已经优化4. 移除 extrasMobX 6 移除了 extras API,相关功能被整合到主 API 中。MobX 4/5:import { extras } from 'mobx';const isObservable = extras.isObservable(obj);MobX 6:import { isObservable } from 'mobx';const isObservable = isObservable(obj);5. 移除 intercept 和 observeMobX 6 移除了 intercept 和 observe API,推荐使用 reaction 替代。MobX 4/5:import { observe } from 'mobx';const disposer = observe(store.todos, (change) => { console.log('Todo changed:', change);});MobX 6:import { reaction } from 'mobx';const disposer = reaction( () => store.todos.length, (length) => { console.log('Todo count changed:', length); });6. 类型推断改进MobX 6 对 TypeScript 的支持更好,类型推断更准确。import { makeAutoObservable } from 'mobx';class TodoStore { todos: Todo[] = []; filter: 'all' | 'completed' | 'active' = 'all'; constructor() { makeAutoObservable<TodoStore>(this); } get completedTodos(): Todo[] { return this.todos.filter(todo => todo.completed); } addTodo(text: string): void { this.todos.push({ text, completed: false }); }}7. 移除 mobx-react 的 inject 和 ProviderMobX 6 推荐使用 React Context API,不再需要 inject 和 Provider。MobX 4/5:import { Provider, inject, observer } from 'mobx-react';@inject('todoStore')@observerclass TodoList extends React.Component { render() { const { todoStore } = this.props; return <div>{/* ... */}</div>; }}function App() { return ( <Provider todoStore={todoStore}> <TodoList /> </Provider> );}MobX 6:import { observer } from 'mobx-react-lite';import { createContext, useContext } from 'react';const TodoContext = createContext(null);function TodoProvider({ children, store }) { return ( <TodoContext.Provider value={store}> {children} </TodoContext.Provider> );}function useTodoStore() { const store = useContext(TodoContext); if (!store) { throw new Error('useTodoStore must be used within TodoProvider'); } return store;}const TodoList = observer(() => { const store = useTodoStore(); return <div>{/* ... */}</div>;});function App() { return ( <TodoProvider store={todoStore}> <TodoList /> </TodoProvider> );}8. 性能改进MobX 6 在性能方面有许多改进:更小的包体积:通过 Tree-shaking 减少包体积更快的响应速度:优化了依赖追踪算法更好的内存管理:减少了内存占用9. 错误处理改进MobX 6 提供了更清晰的错误信息。// MobX 6 会提供更清晰的错误信息class Store { data = []; constructor() { makeAutoObservable(this); } // 如果在 action 外修改状态 modifyData() { this.data.push({}); // 警告:在 action 外修改状态 }}迁移指南从 MobX 4/5 迁移到 MobX 61. 移除装饰器之前:class Store { @observable data = []; @action addData(item) { this.data.push(item); }}之后:class Store { data = []; constructor() { makeAutoObservable(this); } addData(item) { this.data.push(item); }}2. 更新 mobx-react之前:import { Provider, inject, observer } from 'mobx-react';@inject('store')@observerclass Component extends React.Component { render() { const { store } = this.props; return <div>{store.data}</div>; }}之后:import { observer } from 'mobx-react-lite';const Component = observer(() => { const store = useStore(); return <div>{store.data}</div>;});3. 移除 configure之前:import { configure } from 'mobx';configure({ enforceActions: 'always'});之后:// 不再需要 configure4. 更新 extras之前:import { extras } from 'mobx';if (extras.isObservable(obj)) { // ...}之后:import { isObservable } from 'mobx';if (isObservable(obj)) { // ...}最佳实践1. 使用 makeAutoObservableclass Store { data = []; constructor() { makeAutoObservable(this); }}2. 使用 mobx-react-liteimport { observer } from 'mobx-react-lite';const Component = observer(() => { return <div>{/* ... */}</div>;});3. 使用 React Contextconst StoreContext = createContext(null);function StoreProvider({ children, store }) { return ( <StoreContext.Provider value={store}> {children} </StoreContext.Provider> );}function useStore() { const store = useContext(StoreContext); if (!store) { throw new Error('useStore must be used within StoreProvider'); } return store;}4. 使用 TypeScriptclass Store { data: Data[] = []; constructor() { makeAutoObservable<Store>(this); }}常见问题1. 如何继续使用装饰器?如果需要继续使用装饰器,可以安装 mobx-undecorate 包。import { decorate, observable, action } from 'mobx';class Store { data = []; addData(item) { this.data.push(item); }}decorate(Store, { data: observable, addData: action});2. 如何处理类型推断?使用 makeAutoObservable 时,可以传入泛型参数。class Store { data: Data[] = []; constructor() { makeAutoObservable<Store>(this); }}3. 如何处理 action.bound?使用 action.bound 时,需要在 makeObservable 中指定。class Store { data = []; constructor() { makeObservable(this, { data: observable, addData: action.bound }); } addData(item) { this.data.push(item); }}总结MobX 6 移除了装饰器支持,推荐使用 makeAutoObservable移除了 configure、extras、intercept、observe 等 API推荐使用 mobx-react-lite 和 React Context对 TypeScript 的支持更好性能和错误处理都有改进迁移相对简单,主要是替换装饰器语法
阅读 0·2月22日 14:08

什么是 Promise?Promise 有哪些状态?

Promise 是 JavaScript 中处理异步操作的重要机制,它代表一个异步操作的最终完成或失败。Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。一旦状态改变,就不会再变。核心概念Promise 的三种状态pending: 初始状态,既不是成功也不是失败fulfilled: 操作成功完成rejected: 操作失败基本用法const promise = new Promise((resolve, reject) => { // 异步操作 setTimeout(() => { if (/* 操作成功 */) { resolve('成功的结果'); } else { reject('失败的原因'); } }, 1000);});promise.then(result => { console.log(result); // 处理成功}).catch(error => { console.log(error); // 处理失败});Promise 链式调用fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => console.log(posts)) .catch(error => console.error(error));Promise 静态方法Promise.all(): 所有 Promise 都成功才返回成功,有一个失败就返回失败Promise.all([promise1, promise2, promise3]) .then(results => console.log(results)) .catch(error => console.error(error));Promise.race(): 返回最先完成的结果(无论成功或失败)Promise.race([promise1, promise2]) .then(result => console.log(result));Promise.allSettled(): 等待所有 Promise 完成,返回每个 Promise 的状态Promise.allSettled([promise1, promise2]) .then(results => console.log(results));Promise.any(): 返回第一个成功的 PromisePromise.any([promise1, promise2]) .then(result => console.log(result));常见面试问题1. Promise 和回调函数的区别Promise 解决了回调地狱问题Promise 提供了更好的错误处理机制Promise 支持链式调用,代码更清晰Promise 状态不可逆,更符合直觉2. 如何处理 Promise 错误使用 .catch() 方法捕获错误,或者在 .then() 的第二个参数中处理:promise.then( result => console.log(result), error => console.error(error));// 或者promise.then(result => console.log(result)) .catch(error => console.error(error));3. Promise 的微任务机制Promise 的回调属于微任务,会在当前宏任务执行完后立即执行,优先级高于宏任务(如 setTimeout)。4. async/await 与 Promise 的关系async/await 是 Promise 的语法糖,让异步代码看起来像同步代码:async function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); return data; } catch (error) { console.error(error); }}最佳实践总是处理错误: 使用 .catch() 或 try/catch避免嵌套 Promise: 使用链式调用合理使用 Promise 静态方法: 根据场景选择合适的方法理解事件循环: 掌握微任务和宏任务的执行顺序性能优化: 避免不必要的 Promise 包装
阅读 0·2月22日 14:08

如何实现 Promise 的取消?

Promise 的取消是一个常见但复杂的问题。Promise 本身不支持取消,但我们可以通过一些技巧来实现类似的功能。为什么 Promise 不能被取消?Promise 的设计遵循"不可逆"原则:一旦 Promise 状态改变(pending → fulfilled 或 pending → rejected),就不能再改变这种设计保证了 Promise 的可靠性和可预测性取消操作会引入额外的复杂性和不确定性实现取消的几种方法1. 使用 AbortControllerAbortController 是现代浏览器提供的取消异步操作的标准 API。基本用法const controller = new AbortController();const signal = controller.signal;fetch('/api/data', { signal }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => { if (error.name === 'AbortError') { console.log('请求被取消'); } else { console.error('请求失败:', error); } });// 取消请求controller.abort();封装可取消的 fetchfunction fetchWithTimeout(url, options = {}, timeout = 5000) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); return fetch(url, { ...options, signal: controller.signal }) .then(response => { clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .catch(error => { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error('请求超时'); } throw error; });}// 使用示例fetchWithTimeout('/api/data', {}, 3000) .then(data => console.log(data)) .catch(error => console.error(error));2. 使用包装函数通过包装函数来实现取消功能。基本实现function makeCancellable(promise) { let hasCancelled = false; const wrappedPromise = new Promise((resolve, reject) => { promise.then( value => { if (!hasCancelled) resolve(value); }, error => { if (!hasCancelled) reject(error); } ); }); return { promise: wrappedPromise, cancel: () => { hasCancelled = true; } };}// 使用示例const { promise, cancel } = makeCancellable( fetch('/api/data').then(r => r.json()));promise .then(data => console.log(data)) .catch(error => { if (error.name === 'CancellationError') { console.log('操作被取消'); } else { console.error('操作失败:', error); } });// 取消操作cancel();完整实现class CancellablePromise { constructor(executor) { this.isCancelled = false; this.rejectors = []; this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; executor( value => { if (!this.isCancelled) { resolve(value); } }, error => { if (!this.isCancelled) { reject(error); } } ); }); } cancel() { this.isCancelled = true; this.rejectors.forEach(rejector => { rejector(new Error('Promise cancelled')); }); } then(onFulfilled, onRejected) { const promise = this.promise.then(onFulfilled, onRejected); return new CancellablePromise((resolve, reject) => { this.rejectors.push(reject); promise.then(resolve, reject); }); } catch(onRejected) { return this.then(null, onRejected); } finally(onFinally) { return this.then( value => { onFinally(); return value; }, error => { onFinally(); throw error; } ); }}// 使用示例const cancellablePromise = new CancellablePromise((resolve) => { setTimeout(() => { resolve('完成'); }, 2000);});cancellablePromise .then(result => console.log(result)) .catch(error => console.error(error));// 取消setTimeout(() => { cancellablePromise.cancel();}, 1000);3. 使用令牌(Token)模式通过传递令牌来检查是否应该继续执行。class CancellationToken { constructor() { this.isCancelled = false; } cancel() { this.isCancelled = true; } throwIfCancelled() { if (this.isCancelled) { throw new Error('Operation cancelled'); } }}function fetchWithToken(url, token) { return fetch(url) .then(response => { token.throwIfCancelled(); return response.json(); }) .then(data => { token.throwIfCancelled(); return data; });}// 使用示例const token = new CancellationToken();fetchWithToken('/api/data', token) .then(data => console.log(data)) .catch(error => { if (error.message === 'Operation cancelled') { console.log('操作被取消'); } else { console.error('操作失败:', error); } });// 取消操作setTimeout(() => { token.cancel();}, 1000);4. 使用 Promise.race 实现超时function promiseWithTimeout(promise, timeout = 5000) { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(new Error('Timeout')); }, timeout); }); return Promise.race([promise, timeoutPromise]);}// 使用示例promiseWithTimeout( fetch('/api/data').then(r => r.json()), 3000) .then(data => console.log(data)) .catch(error => { if (error.message === 'Timeout') { console.log('请求超时'); } else { console.error('请求失败:', error); } });实际应用场景1. 取消重复的搜索请求class SearchService { constructor() { this.currentController = null; } async search(query) { // 取消之前的请求 if (this.currentController) { this.currentController.abort(); } this.currentController = new AbortController(); try { const response = await fetch(`/api/search?q=${query}`, { signal: this.currentController.signal }); return await response.json(); } catch (error) { if (error.name === 'AbortError') { console.log('搜索请求被取消'); return null; } throw error; } }}// 使用示例const searchService = new SearchService();// 用户快速输入,只保留最后一次搜索searchService.search('hello');searchService.search('hello world');searchService.search('hello world example');2. 取消长时间运行的任务class TaskManager { constructor() { this.tasks = new Map(); } async runTask(taskId, task) { const controller = new AbortController(); this.tasks.set(taskId, controller); try { const result = await task(controller.signal); return result; } catch (error) { if (error.name === 'AbortError') { console.log(`任务 ${taskId} 被取消`); return null; } throw error; } finally { this.tasks.delete(taskId); } } cancelTask(taskId) { const controller = this.tasks.get(taskId); if (controller) { controller.abort(); } }}// 使用示例const taskManager = new TaskManager();// 运行任务taskManager.runTask('task1', async (signal) => { for (let i = 0; i < 10; i++) { signal.throwIfAborted(); await new Promise(resolve => setTimeout(resolve, 1000)); console.log(`步骤 ${i + 1} 完成`); } return '任务完成';});// 取消任务setTimeout(() => { taskManager.cancelTask('task1');}, 3000);3. 组件卸载时取消请求class Component { constructor() { this.controller = new AbortController(); } async fetchData() { try { const response = await fetch('/api/data', { signal: this.controller.signal }); return await response.json(); } catch (error) { if (error.name === 'AbortError') { console.log('组件卸载,请求被取消'); return null; } throw error; } } destroy() { this.controller.abort(); }}// 使用示例const component = new Component();component.fetchData().then(data => { if (data) { console.log('数据加载成功:', data); }});// 组件卸载时setTimeout(() => { component.destroy();}, 1000);最佳实践1. 总是清理资源async function fetchData() { const controller = new AbortController(); try { const response = await fetch('/api/data', { signal: controller.signal }); return await response.json(); } finally { controller.abort(); }}2. 提供取消回调function fetchWithCancel(url, onCancel) { const controller = new AbortController(); const promise = fetch(url, { signal: controller.signal }) .then(response => response.json()); promise.cancel = () => { controller.abort(); if (onCancel) { onCancel(); } }; return promise;}// 使用示例const promise = fetchWithCancel('/api/data', () => { console.log('请求被取消');});promise.then(data => console.log(data));// 取消请求promise.cancel();3. 处理取消错误async function fetchWithCancellation(url) { const controller = new AbortController(); try { const response = await fetch(url, { signal: controller.signal }); return await response.json(); } catch (error) { if (error.name === 'AbortError') { console.log('请求被取消'); return null; } throw error; }}总结Promise 本身不支持取消:需要通过其他方式实现AbortController 是标准方案:现代浏览器推荐使用包装函数提供灵活性:可以根据需求定制取消逻辑令牌模式适合复杂场景:可以精细控制取消时机总是清理资源:避免内存泄漏处理取消错误:区分取消错误和其他错误提供取消回调:让调用者知道操作被取消考虑用户体验:取消操作应该快速响应
阅读 0·2月22日 14:07

Promise 的常见陷阱和最佳实践有哪些?

Promise 的常见陷阱和最佳实践是每个 JavaScript 开发者都应该掌握的知识。了解这些陷阱可以帮助我们写出更健壮、更高效的异步代码。常见陷阱1. 忘记返回 Promise问题示例:// 不推荐:忘记返回 Promisefunction fetchData() { fetch('/api/data') .then(response => response.json()) .then(data => { console.log(data); // 忘记返回数据 });}// 调用者无法获取数据fetchData().then(data => { console.log(data); // undefined});正确做法:// 推荐:返回 Promisefunction fetchData() { return fetch('/api/data') .then(response => response.json()) .then(data => { console.log(data); return data; // 返回数据 });}// 调用者可以获取数据fetchData().then(data => { console.log(data); // 实际数据});2. 在 then 中嵌套 Promise问题示例:// 不推荐:嵌套 Promisefetch('/api/user') .then(response => response.json()) .then(user => { fetch(`/api/posts/${user.id}`) .then(response => response.json()) .then(posts => { console.log(posts); }); });正确做法:// 推荐:扁平链式调用fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => console.log(posts));3. 忘记处理错误问题示例:// 不推荐:没有错误处理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));4. 在循环中顺序 await问题示例:// 不推荐:顺序执行,速度慢async function processItems(items) { const results = []; for (const item of items) { const result = await processItem(item); results.push(result); } return results;}正确做法:// 推荐:并行执行,速度快async function processItems(items) { const promises = items.map(item => processItem(item)); return await Promise.all(promises);}5. 混用 async/await 和 Promise.then()问题示例:// 不推荐:混用导致代码混乱async function fetchData() { const response = await fetch('/api/data'); return response.json().then(data => { console.log(data); return data; });}正确做法:// 推荐:统一使用 async/awaitasync function fetchData() { const response = await fetch('/api/data'); const data = await response.json(); console.log(data); return data;}6. 不必要的 Promise 包装问题示例:// 不推荐:不必要的 Promise 包装function fetchData() { return new Promise((resolve) => { fetch('/api/data') .then(response => response.json()) .then(data => resolve(data)); });}正确做法:// 推荐:直接返回 Promisefunction fetchData() { return fetch('/api/data') .then(response => response.json());}7. 在构造函数中执行异步操作问题示例:// 不推荐:在构造函数中执行异步操作class User { constructor(id) { this.id = id; this.data = null; fetch(`/api/users/${id}`) .then(response => response.json()) .then(data => { this.data = data; }); } getData() { return this.data; // 可能返回 null }}正确做法:// 推荐:使用静态工厂方法或初始化方法class User { constructor(id, data) { this.id = id; this.data = data; } static async create(id) { const response = await fetch(`/api/users/${id}`); const data = await response.json(); return new User(id, data); } getData() { return this.data; // 保证有数据 }}// 使用const user = await User.create(1);console.log(user.getData());8. 过度使用 Promise.all问题示例:// 不推荐:对不相关的操作使用 Promise.allasync function fetchData() { const [user, posts, comments] = await Promise.all([ fetchUser(), fetchPosts(), fetchComments() ]); // 如果只需要用户数据,其他请求是浪费的 return user;}正确做法:// 推荐:只请求需要的数据async function fetchData() { const user = await fetchUser(); return user;}// 或者:按需加载async function fetchAllData() { const user = await fetchUser(); // 只在需要时加载其他数据 if (user.hasPosts) { const posts = await fetchPosts(); return { user, posts }; } return { user };}最佳实践1. 总是返回 Promise// 推荐:函数总是返回 Promisefunction fetchData() { return fetch('/api/data') .then(response => response.json());}// 调用者可以链式调用fetchData() .then(data => processData(data)) .then(result => console.log(result));2. 使用 async/await 提高可读性// 推荐:使用 async/awaitasync function fetchUserData() { try { const userResponse = await fetch('/api/user'); const user = await userResponse.json(); const postsResponse = await fetch(`/api/posts/${user.id}`); const posts = await postsResponse.json(); return { user, posts }; } catch (error) { console.error('获取数据失败:', error); throw error; }}3. 合理使用 Promise.all// 推荐:对独立的异步操作使用 Promise.allasync function fetchAllData() { const [user, posts, comments] = await Promise.all([ fetchUser(), fetchPosts(), fetchComments() ]); return { user, posts, comments };}4. 使用 Promise.allSettled 处理部分失败// 推荐:使用 Promise.allSettledasync function fetchMultipleUrls(urls) { const results = await Promise.allSettled( urls.map(url => fetch(url)) ); const successful = results .filter(r => r.status === 'fulfilled') .map(r => r.value); const failed = results .filter(r => r.status === 'rejected') .map(r => r.reason); return { successful, failed };}5. 使用 Promise.race 实现超时// 推荐:使用 Promise.race 实现超时function fetchWithTimeout(url, timeout = 5000) { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Timeout')), timeout); }); return Promise.race([ fetch(url), timeoutPromise ]);}6. 使用 Promise.any 获取第一个成功的结果// 推荐:使用 Promise.anyasync function fetchFromMultipleSources(sources) { try { const response = await Promise.any( sources.map(source => fetch(source.url)) ); return await response.json(); } catch (error) { if (error instanceof AggregateError) { console.error('所有数据源都失败了'); throw new Error('无法获取数据'); } throw error; }}7. 使用 finally 进行清理// 推荐:使用 finallyasync function fetchDataWithCleanup() { let connection; try { connection = await createConnection(); const data = await connection.query('SELECT * FROM users'); return data; } catch (error) { console.error('查询失败:', error); throw error; } finally { if (connection) { await connection.close(); } }}8. 避免在循环中创建 Promisejavascript// 不推荐:在循环中创建 Promisefunction processItems(items) { const results = [];for (const item of items) { const promise = new Promise((resolve) => { setTimeout(() => { resolve(processItem(item)); }, 1000); }); results.push(promise); }return Promise.all(results);}**正确做法:**javascript// 推荐:使用 map 创建 Promisefunction processItems(items) { const promises = items.map(item => new Promise((resolve) => { setTimeout(() => { resolve(processItem(item)); }, 1000); }) );return Promise.all(promises);}### 9. 使用 AbortController 取消请求javascript// 推荐:使用 AbortControllerclass DataFetcher { constructor() { this.controller = new AbortController(); }async fetch(url) { try { const response = await fetch(url, { signal: this.controller.signal }); return await response.json(); } catch (error) { if (error.name === 'AbortError') { console.log('请求被取消'); return null; } throw error; } }cancel() { this.controller.abort(); }}### 10. 实现重试机制javascript// 推荐:实现重试机制async function fetchWithRetry(url, maxRetries = 3) { for (let i = 0; i HTTP error! status: ${response.status}); } return await response.json(); } catch (error) { console.error(尝试 ${i + 1} 失败:, error.message); if (i === maxRetries - 1) { throw error; } await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)) ); } }}## 性能优化### 1. 避免不必要的 awaitjavascript// 不推荐:不必要的 awaitasync function fetchData() { const data1 = await fetch('/api/data1'); const data2 = await fetch('/api/data2'); const data3 = await fetch('/api/data3'); return { data1, data2, data3 };}// 推荐:并行执行async function fetchData() { const [data1, data2, data3] = await Promise.all( fetch('/api/data1'), fetch('/api/data2'), fetch('/api/data3') ); return { data1, data2, data3 };}### 2. 使用缓存javascript// 推荐:使用缓存const cache = new Map();async function fetchWithCache(url) { if (cache.has(url)) { return cache.get(url); }const data = await fetch(url).then(r => r.json()); cache.set(url, data); return data;}### 3. 实现请求去重javascript// 推荐:实现请求去重const pendingRequests = new Map();async function fetchDeduplicated(url) { if (pendingRequests.has(url)) { return pendingRequests.get(url); }const promise = fetch(url) .then(response => response.json()) .finally(() => { pendingRequests.delete(url); });pendingRequests.set(url, promise); return promise;}```总结总是返回 Promise:让调用者可以链式调用使用 async/await:提高代码可读性避免嵌套 Promise:保持代码扁平处理错误:总是添加错误处理合理使用 Promise.all:对独立的异步操作使用使用 Promise.allSettled:处理部分失败的场景使用 Promise.race:实现超时和竞争使用 Promise.any:获取第一个成功的结果使用 finally:进行清理工作实现重试机制:提高可靠性使用 AbortController:取消请求避免不必要的 await:并行执行独立的异步操作使用缓存:减少重复请求实现请求去重:避免同时发起相同的请求
阅读 0·2月22日 14:07

如何实现 Promise 的并发控制?

Promise 的并发控制是一个重要的性能优化技术,它允许我们限制同时执行的异步操作数量,避免资源耗尽或性能下降。为什么需要并发控制问题场景// 不推荐:同时发起大量请求async function fetchAllUrls(urls) { const promises = urls.map(url => fetch(url)); const results = await Promise.all(promises); return results;}// 问题:// 1. 可能导致浏览器或服务器资源耗尽// 2. 网络带宽可能被占满// 3. 可能触发服务器的限流机制// 4. 内存占用过高基本并发控制实现1. 使用 p-limit 库import pLimit from 'p-limit';async function fetchWithLimit(urls, concurrency = 5) { const limit = pLimit(concurrency); const promises = urls.map(url => limit(() => fetch(url)) ); const results = await Promise.all(promises); return results;}2. 手动实现并发控制async function asyncPool(poolLimit, array, iteratorFn) { const ret = []; const executing = []; for (const item of array) { const p = Promise.resolve().then(() => iteratorFn(item)); ret.push(p); if (poolLimit <= array.length) { const e = p.then(() => executing.splice(executing.indexOf(e), 1)); executing.push(e); if (executing.length >= poolLimit) { await Promise.race(executing); } } } return Promise.all(ret);}// 使用示例async function fetchWithConcurrency(urls, concurrency = 5) { return asyncPool(concurrency, urls, url => fetch(url));}3. 使用队列实现class ConcurrencyControl { constructor(concurrency) { this.concurrency = concurrency; this.queue = []; this.running = 0; } async run(task) { if (this.running >= this.concurrency) { await new Promise(resolve => this.queue.push(resolve)); } this.running++; try { return await task(); } finally { this.running--; const next = this.queue.shift(); if (next) next(); } }}// 使用示例async function fetchWithControl(urls, concurrency = 5) { const control = new ConcurrencyControl(concurrency); const promises = urls.map(url => control.run(() => fetch(url)) ); return Promise.all(promises);}高级并发控制实现1. 带重试机制的并发控制class ConcurrencyControlWithRetry { constructor(concurrency, maxRetries = 3) { this.concurrency = concurrency; this.maxRetries = maxRetries; this.queue = []; this.running = 0; } async run(task) { if (this.running >= this.concurrency) { await new Promise(resolve => this.queue.push(resolve)); } this.running++; let lastError; for (let i = 0; i < this.maxRetries; i++) { try { const result = await task(); return result; } catch (error) { lastError = error; if (i < this.maxRetries - 1) { await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)) ); } } } throw lastError; }}// 使用示例async function fetchWithRetry(urls, concurrency = 5) { const control = new ConcurrencyControlWithRetry(concurrency, 3); const promises = urls.map(url => control.run(() => fetch(url)) ); return Promise.all(promises);}2. 带超时的并发控制class ConcurrencyControlWithTimeout { constructor(concurrency, timeout = 5000) { this.concurrency = concurrency; this.timeout = timeout; this.queue = []; this.running = 0; } async run(task) { if (this.running >= this.concurrency) { await new Promise(resolve => this.queue.push(resolve)); } this.running++; try { const result = await Promise.race([ task(), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), this.timeout) ) ]); return result; } finally { this.running--; const next = this.queue.shift(); if (next) next(); } }}// 使用示例async function fetchWithTimeout(urls, concurrency = 5) { const control = new ConcurrencyControlWithTimeout(concurrency, 5000); const promises = urls.map(url => control.run(() => fetch(url)) ); return Promise.all(promises);}3. 带优先级的并发控制class PriorityConcurrencyControl { constructor(concurrency) { this.concurrency = concurrency; this.queue = []; this.running = 0; } async run(task, priority = 0) { const taskWrapper = { task, priority, resolve: null }; this.queue.push(taskWrapper); this.queue.sort((a, b) => b.priority - a.priority); if (this.running >= this.concurrency) { await new Promise(resolve => { taskWrapper.resolve = resolve; }); } this.running++; try { return await task(); } finally { this.running--; const next = this.queue.find(t => t.resolve); if (next) { this.queue.splice(this.queue.indexOf(next), 1); next.resolve(); } } }}// 使用示例async function fetchWithPriority(urls, concurrency = 5) { const control = new PriorityConcurrencyControl(concurrency); const promises = urls.map((url, index) => control.run(() => fetch(url), index % 3) ); return Promise.all(promises);}实际应用场景1. 批量下载文件async function downloadFiles(urls, concurrency = 3) { const control = new ConcurrencyControl(concurrency); const results = await Promise.all( urls.map(url => control.run(async () => { const response = await fetch(url); const blob = await response.blob(); return { url, blob }; }) ) ); return results;}2. 批量处理数据库查询async function processQueries(queries, concurrency = 5) { const control = new ConcurrencyControl(concurrency); const results = await Promise.all( queries.map(query => control.run(() => database.execute(query)) ) ); return results;}3. 批量发送邮件async function sendEmails(recipients, concurrency = 10) { const control = new ConcurrencyControl(concurrency); const results = await Promise.allSettled( recipients.map(recipient => control.run(() => emailService.send(recipient)) ) ); const successful = results.filter(r => r.status === 'fulfilled').length; const failed = results.filter(r => r.status === 'rejected').length; console.log(`发送完成: 成功 ${successful}, 失败 ${failed}`); return results;}性能优化技巧1. 动态调整并发数class AdaptiveConcurrencyControl { constructor(initialConcurrency = 5, maxConcurrency = 20) { this.concurrency = initialConcurrency; this.maxConcurrency = maxConcurrency; this.minConcurrency = 1; this.queue = []; this.running = 0; this.successCount = 0; this.errorCount = 0; } async run(task) { if (this.running >= this.concurrency) { await new Promise(resolve => this.queue.push(resolve)); } this.running++; try { const result = await task(); this.successCount++; this.adjustConcurrency(); return result; } catch (error) { this.errorCount++; this.adjustConcurrency(); throw error; } finally { this.running--; const next = this.queue.shift(); if (next) next(); } } adjustConcurrency() { const total = this.successCount + this.errorCount; const errorRate = this.errorCount / total; if (errorRate < 0.1 && this.concurrency < this.maxConcurrency) { this.concurrency = Math.min(this.concurrency + 1, this.maxConcurrency); } else if (errorRate > 0.3 && this.concurrency > this.minConcurrency) { this.concurrency = Math.max(this.concurrency - 1, this.minConcurrency); } }}2. 进度监控class ConcurrencyControlWithProgress { constructor(concurrency, onProgress) { this.concurrency = concurrency; this.onProgress = onProgress; this.queue = []; this.running = 0; this.completed = 0; this.total = 0; } async run(task) { this.total++; if (this.running >= this.concurrency) { await new Promise(resolve => this.queue.push(resolve)); } this.running++; try { const result = await task(); return result; } finally { this.running++; this.completed++; this.onProgress(this.completed, this.total); this.running--; const next = this.queue.shift(); if (next) next(); } }}// 使用示例async function fetchWithProgress(urls, concurrency = 5) { const control = new ConcurrencyControlWithProgress(concurrency, (completed, total) => { console.log(`进度: ${completed}/${total} (${(completed/total*100).toFixed(1)}%)`); }); const promises = urls.map(url => control.run(() => fetch(url)) ); return Promise.all(promises);}常见问题1. 如何选择合适的并发数?// 根据网络类型选择function getOptimalConcurrency() { const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; if (connection) { return Math.min(connection.downlink || 4, 10); } return 4; // 默认值}2. 如何处理失败的任务?async function fetchWithPartialFailure(urls, concurrency = 5) { const control = new ConcurrencyControl(concurrency); const results = await Promise.allSettled( urls.map(url => control.run(() => fetch(url)) ) ); const successful = results.filter(r => r.status === 'fulfilled'); const failed = results.filter(r => r.status === 'rejected'); console.log(`成功: ${successful.length}, 失败: ${failed.length}`); return { successful, failed };}总结避免资源耗尽:限制同时执行的异步操作数量提高性能:合理的并发控制可以提高整体性能灵活实现:可以根据需求实现不同的并发控制策略错误处理:结合重试、超时等机制提高可靠性进度监控:实时监控任务执行进度动态调整:根据实际情况动态调整并发数
阅读 0·2月22日 14:07

如何优化 Promise 的性能?

Promise 的性能优化是提升应用响应速度和用户体验的关键。通过合理使用 Promise 和相关技术,可以显著提高异步操作的效率。避免不必要的 Promise 包装问题示例// 不推荐:不必要的 Promise 包装function fetchData() { return new Promise((resolve) => { resolve(fetch('/api/data')); });}// 推荐:直接返回 Promisefunction fetchData() { return fetch('/api/data');}优化原因不必要的 Promise 包装会增加额外的开销,包括:创建新的 Promise 对象额外的微任务调度增加内存占用并行执行独立操作顺序执行(慢)// 不推荐:顺序执行async function fetchAllData() { const user = await fetchUser(); const posts = await fetchPosts(); const comments = await fetchComments(); return { user, posts, comments };}并行执行(快)// 推荐:并行执行async function fetchAllData() { const [user, posts, comments] = await Promise.all([ fetchUser(), fetchPosts(), fetchComments() ]); return { user, posts, comments };}性能对比假设每个请求需要 100ms:顺序执行:300ms并行执行:100ms(3倍提升)避免过长的 Promise 链问题示例// 不推荐:过长的 Promise 链function processLargeData(data) { return Promise.resolve(data) .then(data => processData1(data)) .then(data => processData2(data)) .then(data => processData3(data)) .then(data => processData4(data)) .then(data => processData5(data)) .then(data => processData6(data)) .then(data => processData7(data)) .then(data => processData8(data));}优化方案// 推荐:使用 async/awaitasync function processLargeData(data) { data = await processData1(data); data = await processData2(data); data = await processData3(data); data = await processData4(data); data = await processData5(data); data = await processData6(data); data = await processData7(data); data = await processData8(data); return data;}// 或者:使用函数组合function processLargeData(data) { return [processData1, processData2, processData3, processData4, processData5, processData6, processData7, processData8] .reduce((promise, processor) => promise.then(processor), Promise.resolve(data) );}合理使用缓存基本缓存实现const cache = new Map();function fetchWithCache(url) { if (cache.has(url)) { return Promise.resolve(cache.get(url)); } return fetch(url) .then(response => response.json()) .then(data => { cache.set(url, data); return data; });}带过期时间的缓存class PromiseCache { constructor(ttl = 60000) { this.cache = new Map(); this.ttl = ttl; } get(key) { const item = this.cache.get(key); if (!item) return null; if (Date.now() > item.expiry) { this.cache.delete(key); return null; } return item.value; } set(key, value) { this.cache.set(key, { value, expiry: Date.now() + this.ttl }); } async fetch(key, fetcher) { const cached = this.get(key); if (cached) return cached; const value = await fetcher(); this.set(key, value); return value; }}// 使用示例const cache = new PromiseCache(60000);async function fetchUser(id) { return cache.fetch(`user:${id}`, () => fetch(`/api/users/${id}`).then(r => r.json()) );}请求去重基本去重实现const pendingRequests = new Map();function fetchDeduplicated(url) { if (pendingRequests.has(url)) { return pendingRequests.get(url); } const promise = fetch(url) .then(response => response.json()) .finally(() => { pendingRequests.delete(url); }); pendingRequests.set(url, promise); return promise;}完整的去重实现class RequestDeduplicator { constructor() { this.pendingRequests = new Map(); } async fetch(url, options = {}) { const key = this.getRequestKey(url, options); if (this.pendingRequests.has(key)) { return this.pendingRequests.get(key); } const promise = fetch(url, options) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .finally(() => { this.pendingRequests.delete(key); }); this.pendingRequests.set(key, promise); return promise; } getRequestKey(url, options) { return JSON.stringify({ url, options }); }}// 使用示例const deduplicator = new RequestDeduplicator();// 多次调用同一个 URL,只会发起一次请求Promise.all([ deduplicator.fetch('/api/user'), deduplicator.fetch('/api/user'), deduplicator.fetch('/api/user')]).then(results => { console.log('所有请求返回相同结果:', results);});批量处理问题示例// 不推荐:逐个处理async function processItems(items) { const results = []; for (const item of items) { const result = await processItem(item); results.push(result); } return results;}优化方案// 推荐:批量处理async function processItems(items, batchSize = 10) { const results = []; for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); const batchResults = await Promise.all( batch.map(item => processItem(item)) ); results.push(...batchResults); } return results;}错误处理优化避免过度使用 try/catch// 不推荐:过度使用 try/catchasync function fetchData() { try { try { const response = await fetch('/api/data'); try { const data = await response.json(); try { const processed = await processData(data); return processed; } catch (error) { console.error('处理失败:', error); } } catch (error) { console.error('解析失败:', error); } } catch (error) { console.error('请求失败:', error); } } catch (error) { console.error('未知错误:', error); }}优化方案// 推荐:合理的错误处理async function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); return await processData(data); } catch (error) { if (error instanceof NetworkError) { console.error('网络错误:', error.message); } else if (error instanceof ParseError) { console.error('解析错误:', error.message); } else { console.error('未知错误:', error); } throw error; }}内存优化避免内存泄漏// 不推荐:可能导致内存泄漏class DataFetcher { constructor() { this.cache = new Map(); } async fetch(url) { if (this.cache.has(url)) { return this.cache.get(url); } const data = await fetch(url).then(r => r.json()); this.cache.set(url, data); return data; }}优化方案// 推荐:使用 WeakMap 或限制缓存大小class DataFetcher { constructor(maxSize = 100) { this.cache = new Map(); this.maxSize = maxSize; } async fetch(url) { if (this.cache.has(url)) { return this.cache.get(url); } const data = await fetch(url).then(r => r.json()); // 限制缓存大小 if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } this.cache.set(url, data); return data; }}性能监控监控 Promise 执行时间function withPerformanceTracking(promise, label) { const startTime = performance.now(); return promise .then(result => { const duration = performance.now() - startTime; console.log(`${label} 完成,耗时: ${duration.toFixed(2)}ms`); return result; }) .catch(error => { const duration = performance.now() - startTime; console.error(`${label} 失败,耗时: ${duration.toFixed(2)}ms`, error); throw error; });}// 使用示例async function fetchData() { return withPerformanceTracking( fetch('/api/data').then(r => r.json()), 'fetchData' );}监控并发请求数class RequestMonitor { constructor() { this.activeRequests = 0; this.maxConcurrentRequests = 0; } async monitor(promise) { this.activeRequests++; this.maxConcurrentRequests = Math.max( this.activeRequests, this.maxConcurrentRequests ); try { return await promise; } finally { this.activeRequests--; } } getStats() { return { activeRequests: this.activeRequests, maxConcurrentRequests: this.maxConcurrentRequests }; }}// 使用示例const monitor = new RequestMonitor();async function fetchWithMonitor(url) { return monitor.monitor(fetch(url));}总结避免不必要的 Promise 包装:减少额外的开销并行执行独立操作:利用 Promise.all 提高性能避免过长的 Promise 链:使用 async/await 提高可读性合理使用缓存:减少重复请求实现请求去重:避免重复的网络请求批量处理数据:提高处理效率优化错误处理:避免过度嵌套的 try/catch注意内存管理:避免内存泄漏监控性能指标:及时发现性能问题选择合适的并发数:根据实际情况调整并发策略
阅读 0·2月22日 14:07

如何理解 Promise 的链式调用?

Promise 的链式调用是 Promise 最强大的特性之一,它允许我们以优雅的方式处理多个异步操作,避免了回调地狱的问题。基本概念Promise 链式调用是指通过 .then() 方法返回一个新的 Promise,从而可以连续调用多个 .then() 方法。每个 .then() 方法接收前一个 Promise 的结果作为参数,并返回一个新的 Promise。链式调用的工作原理核心机制.then() 方法总是返回一个新的 Promise前一个 .then() 的返回值会传递给下一个 .then()如果返回的是 Promise,会等待其完成后再传递结果错误会沿着链向下传递,直到被 .catch() 捕获示例代码fetch('/api/user') .then(response => response.json()) .then(user => { console.log('用户信息:', user); return fetch(`/api/posts/${user.id}`); }) .then(response => response.json()) .then(posts => { console.log('用户文章:', posts); return posts; }) .catch(error => { console.error('发生错误:', error); });链式调用的返回值处理1. 返回普通值Promise.resolve(1) .then(value => value + 1) .then(value => value * 2) .then(value => console.log(value)); // 输出: 42. 返回 PromisePromise.resolve(1) .then(value => { return Promise.resolve(value + 1); }) .then(value => { return new Promise(resolve => { setTimeout(() => resolve(value * 2), 1000); }); }) .then(value => console.log(value)); // 输出: 43. 不返回值(返回 undefined)Promise.resolve(1) .then(value => { console.log(value); // 输出: 1 // 不返回任何值,相当于返回 undefined }) .then(value => console.log(value)); // 输出: undefined4. 抛出错误Promise.resolve(1) .then(value => { throw new Error('出错了'); }) .catch(error => { console.error(error.message); // 输出: 出错了 return '恢复后的值'; }) .then(value => console.log(value)); // 输出: 恢复后的值错误处理机制错误传递Promise.resolve() .then(() => { throw new Error('第一个错误'); }) .then(() => { console.log('这行不会执行'); }) .catch(error => { console.error('捕获错误:', error.message); return '继续执行'; }) .then(value => { console.log(value); // 输出: 继续执行 });多个 catchPromise.resolve() .then(() => { throw new Error('错误'); }) .catch(error => { console.error('第一个 catch:', error.message); throw new Error('新错误'); }) .catch(error => { console.error('第二个 catch:', error.message); });Promise 链式调用 vs 回调地狱回调地狱(不推荐)fetch('/api/user', (error, response) => { if (error) { console.error(error); return; } response.json((error, user) => { if (error) { console.error(error); return; } fetch(`/api/posts/${user.id}`, (error, response) => { if (error) { console.error(error); return; } response.json((error, posts) => { if (error) { console.error(error); return; } console.log(posts); }); }); });});Promise 链式调用(推荐)fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => console.log(posts)) .catch(error => console.error(error));最佳实践1. 保持链的扁平// 不推荐:嵌套 PromisePromise.resolve() .then(() => { return Promise.resolve().then(() => { return Promise.resolve().then(() => { console.log('嵌套太深'); }); }); });// 推荐:扁平链式Promise.resolve() .then(() => {}) .then(() => {}) .then(() => console.log('扁平清晰'));2. 合理使用 finallyPromise.resolve() .then(() => console.log('执行操作')) .catch(error => console.error(error)) .finally(() => console.log('清理资源'));3. 错误处理要全面Promise.resolve() .then(data => { // 处理数据 return processData(data); }) .catch(error => { // 处理错误 console.error('处理失败:', error); // 可以返回默认值或重新抛出错误 return defaultValue; });4. 避免在 then 中创建不必要的 Promise// 不推荐Promise.resolve(1) .then(value => { return new Promise(resolve => { resolve(value + 1); }); });// 推荐Promise.resolve(1) .then(value => value + 1);常见问题1. 如何在链中传递多个值?Promise.all([promise1, promise2]) .then(([result1, result2]) => { console.log(result1, result2); });2. 如何在链中跳过某些步骤?Promise.resolve() .then(() => { if (shouldSkip) { return Promise.reject('skip'); } return doSomething(); }) .catch(error => { if (error === 'skip') { return '跳过的结果'; } throw error; }) .then(result => console.log(result));3. 如何在链中添加日志?Promise.resolve(1) .tap(value => console.log('当前值:', value)) .then(value => value + 1) .tap(value => console.log('新值:', value));与 async/await 的对比Promise 链式调用fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => console.log(posts)) .catch(error => console.error(error));async/await(更易读)async function fetchPosts() { try { const userResponse = await fetch('/api/user'); const user = await userResponse.json(); const postsResponse = await fetch(`/api/posts/${user.id}`); const posts = await postsResponse.json(); console.log(posts); } catch (error) { console.error(error); }}async/await 本质上是 Promise 链式调用的语法糖,让异步代码看起来更像同步代码,提高了代码的可读性。
阅读 0·2月22日 14:07

如何处理 Promise 的错误?

Promise 的错误处理是使用 Promise 时必须掌握的重要技能。正确的错误处理可以确保程序的健壮性,避免未捕获的错误导致程序崩溃。错误处理的基本方法1. 使用 .catch() 方法.catch() 是 Promise 错误处理的主要方法,它会捕获链中任何地方抛出的错误:Promise.resolve() .then(() => { throw new Error('出错了'); }) .catch(error => { console.error('捕获到错误:', error.message); });2. 在 .then() 的第二个参数中处理.then() 方法可以接收两个参数:成功回调和失败回调:Promise.resolve() .then( result => console.log('成功:', result), error => console.error('失败:', error.message) );注意:这种方式的错误处理只捕获前一个 Promise 的错误,不会捕获链中后续的错误。错误传播机制错误会沿着 Promise 链向下传播Promise.resolve() .then(() => { throw new Error('第一个错误'); }) .then(() => { console.log('这行不会执行'); }) .then(() => { console.log('这行也不会执行'); }) .catch(error => { console.error('最终捕获:', error.message); // 输出: 最终捕获: 第一个错误 });错误可以被捕获后恢复Promise.resolve() .then(() => { throw new Error('出错了'); }) .catch(error => { console.error('捕获错误:', error.message); return '恢复后的值'; // 返回一个值,链可以继续 }) .then(value => { console.log('继续执行:', value); // 输出: 继续执行: 恢复后的值 });常见错误处理场景1. 网络请求错误处理fetch('/api/data') .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { console.log('数据:', data); }) .catch(error => { console.error('请求失败:', error.message); // 可以在这里显示错误提示给用户 showErrorToUser('数据加载失败,请稍后重试'); });2. 多个 Promise 的错误处理Promise.all([promise1, promise2, promise3]) .then(results => { console.log('全部成功:', results); }) .catch(error => { console.error('至少一个失败:', error.message); });3. 使用 Promise.allSettled 处理部分失败Promise.allSettled([promise1, promise2, promise3]) .then(results => { results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(`Promise ${index} 成功:`, result.value); } else { console.error(`Promise ${index} 失败:`, result.reason); } }); });错误处理的最佳实践1. 总是添加错误处理// 不推荐:没有错误处理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));2. 使用 finally 进行清理let isLoading = true;fetch('/api/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error(error)) .finally(() => { isLoading = false; console.log('请求完成,无论成功或失败'); });3. 错误处理要具体// 不推荐:笼统的错误处理Promise.resolve() .catch(error => { console.error('出错了'); });// 推荐:具体的错误处理Promise.resolve() .catch(error => { if (error instanceof NetworkError) { console.error('网络错误:', error.message); } else if (error instanceof ValidationError) { console.error('验证错误:', error.message); } else { console.error('未知错误:', error.message); } });4. 考虑错误恢复策略async function fetchDataWithRetry(url, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error(`尝试 ${i + 1} 失败:`, error.message); if (i === maxRetries - 1) { throw error; // 最后一次尝试失败,抛出错误 } // 等待一段时间后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } }}async/await 中的错误处理使用 try/catchasync function fetchData() { try { const response = await fetch('/api/data'); const data = await response.json(); console.log('数据:', data); } catch (error) { console.error('错误:', error.message); // 错误处理逻辑 }}捕获特定错误async function fetchData() { try { const response = await fetch('/api/data'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { if (error.name === 'TypeError') { console.error('网络连接问题'); } else if (error.message.includes('HTTP error')) { console.error('服务器错误'); } else { console.error('未知错误:', error); } throw error; // 可以选择重新抛出错误 }}未捕获的 Promise 错误全局错误处理// 处理未捕获的 Promise 错误window.addEventListener('unhandledrejection', event => { console.error('未捕获的 Promise 错误:', event.reason); // 可以在这里记录错误或显示错误提示 event.preventDefault(); // 阻止默认的错误输出});// Node.js 环境中process.on('unhandledRejection', (reason, promise) => { console.error('未捕获的 Promise 错误:', reason);});常见错误处理陷阱1. 忘记在链中添加 catch// 危险:没有错误处理Promise.reject('出错了') .then(result => console.log(result)); // 错误会冒泡到全局,可能导致程序崩溃// 安全:添加错误处理Promise.reject('出错了') .then(result => console.log(result)) .catch(error => console.error(error));2. 在 catch 中忘记重新抛出错误// 可能导致问题:错误被吞掉Promise.reject('出错了') .catch(error => { console.error('捕获错误:', error); // 忘记重新抛出,后续代码会继续执行 }) .then(() => { console.log('这行会执行,即使前面有错误'); });// 推荐:根据情况决定是否重新抛出Promise.reject('出错了') .catch(error => { console.error('捕获错误:', error); // 如果错误无法恢复,重新抛出 throw error; });3. 混用 then 的第二个参数和 catch// 不推荐:容易混淆Promise.resolve() .then( result => console.log('成功'), error => console.error('失败1') ) .catch(error => console.error('失败2'));// 推荐:统一使用 catchPromise.resolve() .then(result => console.log('成功')) .catch(error => console.error('失败'));总结总是添加错误处理:使用 .catch() 或 try/catch理解错误传播:错误会沿着 Promise 链向下传播使用 finally 清理:无论成功或失败都要执行的代码放在 finally 中具体化错误处理:根据错误类型进行不同的处理考虑错误恢复:实现重试机制或降级策略避免错误吞掉:确保错误被正确处理或重新抛出全局错误监控:使用全局错误处理器捕获未处理的错误
阅读 0·2月22日 14:07