跨组件监听 Promise 和异步状态机的设计与实现

137 阅读3分钟

在现代前端开发中,异步操作无处不在。当多个组件需要共享异步任务的状态或结果,或者需要以状态机的形式管理复杂流程时,如何优雅地解决这些问题是开发者面临的重要挑战。本文将详细探讨跨组件监听 Promise异步状态机设计与实现 的多种方案。

为什么需要异步状态管理?

在实际开发中,以下场景非常常见:

  1. 跨组件异步状态共享:一个组件发起异步任务,其他组件需要根据任务状态或结果更新自己的逻辑。
  2. 复杂异步流程:多个异步任务串联或并发执行,流程中包含条件分支或状态切换。
  3. 状态一致性与解耦:组件间解耦的同时,保证异步操作的状态同步更新。

这两类问题虽然表面看似不同,但本质上都需要对异步状态进行清晰的管理与传播。


解决方案 1:跨组件监听 Promise

1.1 全局状态管理

适用场景

  • 应用规模较大,多个组件共享异步状态。
  • 使用状态管理工具(如 Redux、MobX、Context)便于集中化管理。

实现代码:基于 Context

import React, { createContext, useContext, useState } from 'react';

const PromiseContext = createContext();

export function PromiseProvider({ children }) {
  const [promiseState, setPromiseState] = useState({ status: 'idle', result: null });

  const resolvePromise = async (promise) => {
    setPromiseState({ status: 'pending', result: null });
    try {
      const result = await promise;
      setPromiseState({ status: 'resolved', result });
    } catch (error) {
      setPromiseState({ status: 'rejected', result: error });
    }
  };

  return (
    <PromiseContext.Provider value={{ promiseState, resolvePromise }}>
      {children}
    </PromiseContext.Provider>
  );
}

export function usePromiseContext() {
  return useContext(PromiseContext);
}

// 使用示例
function ComponentA() {
  const { resolvePromise } = usePromiseContext();

  const handleClick = () => {
    const fetchData = new Promise((resolve) => setTimeout(() => resolve('数据加载完成'), 2000));
    resolvePromise(fetchData);
  };

  return <button onClick={handleClick}>开始加载</button>;
}

function ComponentB() {
  const { promiseState } = usePromiseContext();

  return <div>状态: {promiseState.status} - 结果: {promiseState.result}</div>;
}

function App() {
  return (
    <PromiseProvider>
      <ComponentA />
      <ComponentB />
    </PromiseProvider>
  );
}

1.2 自定义事件系统

通过事件机制传播异步任务的状态,组件只需监听对应事件即可响应状态更新。

实现代码

class PromiseEventEmitter {
  constructor() {
    this.listeners = [];
  }

  on(listener) {
    this.listeners.push(listener);
  }

  emit(data) {
    this.listeners.forEach((listener) => listener(data));
  }
}

const promiseEmitter = new PromiseEventEmitter();

function ComponentA() {
  const handleClick = () => {
    const asyncTask = new Promise((resolve) => setTimeout(() => resolve('任务完成'), 2000));
    asyncTask.then((result) => {
      promiseEmitter.emit({ status: 'resolved', result });
    });
  };

  return <button onClick={handleClick}>触发任务</button>;
}

function ComponentB() {
  React.useEffect(() => {
    const listener = (data) => {
      console.log('监听到Promise完成:', data);
    };
    promiseEmitter.on(listener);

    return () => {
      promiseEmitter.listeners = promiseEmitter.listeners.filter((l) => l !== listener);
    };
  }, []);

  return <div>监听Promise完成</div>;
}

解决方案 2:异步状态机设计与实现

异步状态机是管理复杂流程的利器,特别是当任务之间存在依赖或状态流转时。以下是基于状态机的异步操作管理实现。

2.1 异步状态机的基本思路

  1. 状态定义:明确每个阶段的状态(如 INIT, LOADING, SUCCESS, ERROR)。
  2. 状态转移规则:定义从一个状态到另一个状态的转移条件。
  3. 异步操作嵌入:在状态转移过程中嵌入异步操作。

2.2 示例:用户注册流程

状态机实现代码

class AsyncStateMachine {
  constructor(initialState) {
    this.state = initialState;
    this.context = {};
    this.transitions = {};
  }

  addTransition(state, event, nextState, action) {
    if (!this.transitions[state]) {
      this.transitions[state] = {};
    }
    this.transitions[state][event] = { nextState, action };
  }

  async dispatch(event, payload) {
    const stateTransitions = this.transitions[this.state];
    if (!stateTransitions || !stateTransitions[event]) {
      throw new Error(`无法从状态 "${this.state}" 处理事件 "${event}"`);
    }

    const { nextState, action } = stateTransitions[event];
    if (action) {
      try {
        this.context = await action(payload, this.context);
      } catch (error) {
        console.error('异步操作失败:', error);
        this.state = 'ERROR';
        return;
      }
    }
    this.state = nextState;
    console.log(`状态转移到: ${this.state}`);
  }
}

// 初始化状态机
const stateMachine = new AsyncStateMachine('INIT');

// 定义状态转移规则
stateMachine.addTransition('INIT', 'CHECK_USERNAME', 'USERNAME_CHECKED', async (payload) => {
  console.log('检查用户名...');
  const result = await mockApi('/checkUsername', { username: payload });
  if (!result.available) {
    throw new Error('用户名不可用');
  }
  return { username: payload };
});

stateMachine.addTransition('USERNAME_CHECKED', 'SEND_CODE', 'CODE_SENT', async (payload, context) => {
  console.log('发送验证码...');
  await mockApi('/sendCode', { username: context.username });
  return context;
});

stateMachine.addTransition('CODE_SENT', 'SUBMIT_REGISTRATION', 'REGISTRATION_COMPLETE', async (payload, context) => {
  console.log('提交注册信息...');
  const result = await mockApi('/register', { ...payload, username: context.username });
  return { ...context, registrationData: result };
});

// 模拟 API
function mockApi(endpoint, data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.8) reject(new Error(`请求失败: ${endpoint}`));
      else resolve({ available: true, success: true, data });
    }, 1000);
  });
}

// 执行状态机流程
(async () => {
  try {
    await stateMachine.dispatch('CHECK_USERNAME', 'testUser');
    await stateMachine.dispatch('SEND_CODE');
    await stateMachine.dispatch('SUBMIT_REGISTRATION', { password: '123456' });
    console.log('流程完成!', stateMachine.context);
  } catch (error) {
    console.error('流程出错:', error.message);
  }
})();

2.3 适用场景

  • 多步骤流程:如用户注册、订单支付。
  • 状态依赖:每个状态依赖于前一个异步操作的结果。
  • 条件分支:基于不同状态的条件触发。

总结

跨组件监听 Promise

  • 全局状态管理:推荐用于复杂项目,方便集中化管理。
  • 自定义事件系统:适合轻量级任务的解耦通信。
  • 直接传递 Promise:适合简单的小型项目。
  • 中间层service:适合中型项目

异步状态机

  • 状态机设计可以优雅地处理复杂的异步流程。
  • 状态机的设计核心在于明确状态和状态转移规则
  • 状态机特别适合多步骤、状态依赖强的任务场景。

选择适合的实现方式,可以显著提高代码的可维护性和扩展性。希望本文能为你带来新的思路和灵感!