前端请求库的下一个进化方向:从 Promise 到策略化

摘要:前端请求库的演进正从 Promise 范式走向策略范式。本文以 alova 为例,通过分页列表、表单提交、轮询三个典型场景的 Before/After 代码对比,分析策略化设计如何将"过程式编程"转变为"意图式编程",并讨论其适用边界与局限性。


引言

前端请求库的演进,本质上是对"数据获取"这一高频场景的不断抽象。

从 XMLHttpRequest 的回调时代,到 fetch / axios 的 Promise 范式,再到 React Query、SWR 的声明式 Hooks,每一次进化都在降低样板代码的复杂度。

目前有一个值得关注的方向:将请求模式进一步抽象为"策略"------不再让开发者手动管理 loading、error、分页状态、轮询定时器等样板逻辑,而是通过语义化的 Hook 直接声明意图。

本文以 alova 为例,分析从 Promise 范式到策略范式这一进化路径的设计思路和应用边界。

Promise 时代:一个场景看样板代码

以分页列表为例------这是前端最常见的场景之一。传统 Promise 方案的代码结构大致如下:

jsx 复制代码
// 传统 Promise(axios)方案
import { useState, useEffect, useCallback } from 'react';
import axios from 'axios';

function TodoList() {
  const [list, setList] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [page, setPage] = useState(1);
  const pageSize = 10;
  const [total, setTotal] = useState(0);

  const fetchList = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const res = await axios.get('/api/todos', {
        params: { page, pageSize }
      });
      setList(res.data.list);
      setTotal(res.data.total);
    } catch (e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  }, [page]);

  useEffect(() => { fetchList(); }, [fetchList]);

  return (
    <div>
      {loading && <Spinner />}
      {error && <ErrorMsg message={error} />}
      {list.map(item => <Item key={item.id} {...item} />)}
      <Pagination
        current={page}
        total={total}
        pageSize={pageSize}
        onChange={p => setPage(p)}
      />
    </div>
  );
}

这段代码本身不复杂,但有几个共性问题:

  1. 状态管理重复:loading、error、data 几乎是每个请求都要声明一遍的"三件套"
  2. 请求依赖声明不够直观:useCallback 依赖数组 + useEffect 的搭配,本意是"page 变化时重新请求",但实现上需要三层嵌套
  3. 刷新触发分散:当需要外部触发刷新时(比如新增数据后),需要额外通过状态提升或 ref 来实现
  4. 翻页状态外露:page、pageSize、total 每个都需要手动维护

策略化思路:从"怎么做"到"要什么"

Promise 方案的本质是过程式编程:你需要告诉程序"怎么做"------什么时候发请求、怎么管理加载状态、怎么处理错误、怎么刷新。

策略化的思路反过来:你只需声明"我要什么"------我需要一个分页列表、我需要一个表单提交、我需要一个轮询------库负责执行策略的具体实现。

alova 为这一思路提供了一套面向常见场景的 Hook:

策略 Hook 对应场景
usePagination 分页列表 / 无限滚动
useForm 表单提交(支持草稿、多步表单)
useAutoRequest 轮询 / 焦点刷新 / 重连刷新
useCaptcha 验证码发送 + 倒计时
useUploader 文件上传(支持进度、并发控制)
useRetriableRequest 指数退避重试

三个场景的 Before / After

场景一:分页列表

jsx 复制代码
// alova usePagination
import { usePagination } from 'alova/client';

function TodoList() {
  const {
    data,
    loading,
    error,
    page,
    pageSize,
    total,
    send
  } = usePagination(
    (page, pageSize) => alovaInstance.Get('/api/todos', {
      params: { page, pageSize }
    })
  );

  // loading / error / data / page / pageSize / total 由 Hook 统一管理
  return (
    <div>
      {loading && <Spinner />}
      {error && <ErrorMsg message={error.message} />}
      {data.map(item => <Item key={item.id} {...item} />)}
      <Pagination
        current={page}
        total={total}
        pageSize={pageSize}
        onChange={p => send(p)}
      />
    </div>
  );
}

相比 Promise 版本:

  • 不再需要 useState 管理 5 个状态(loading / error / data / page / total)
  • 不再需要 useCallback + useEffect 声明请求依赖
  • send(p) 替代 setPage(p),语义更明确
  • 内置了翻页状态、预加载和缓存失效逻辑

场景二:表单提交

jsx 复制代码
// 传统 Promise 方式
function LoginForm() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    setError(null);
    try {
      await axios.post('/api/login', formData);
      router.push('/dashboard');
    } catch (e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {error && <Alert type="error">{error}</Alert>}
      <button disabled={loading}>登录</button>
    </form>
  );
}
jsx 复制代码
// alova useForm
function LoginForm() {
  const { loading, error, send } = useForm(
    (formData) => alovaInstance.Post('/api/login', formData),
    { resetAfterSubmitting: true }
  );

  return (
    <form onSubmit={e => send(e)}>
      {error && <Alert type="error">{error.message}</Alert>}
      <button disabled={loading}>登录</button>
    </form>
  );
}

useForm 将 loading / error 管理和 try-catch-final 流程内聚到 Hook 内部。额外的能力如草稿持久化、多步表单状态共享也可以通过配置开启,无需额外代码。

场景三:轮询

jsx 复制代码
// 传统方式:useEffect + setInterval
useEffect(() => {
  const fetchStatus = async () => {
    const { data } = await axios.get('/api/status');
    setStatus(data);
  };
  fetchStatus();
  const timer = setInterval(fetchStatus, 3000);
  return () => clearInterval(timer);
}, []);

// alova useAutoRequest
useAutoRequest(
  () => alovaInstance.Get('/api/status'),
  {
    pollingInterval: 3000,
    enablePolling: true
  }
);

useAutoRequest 除了轮询,还内置了页面焦点变化自动刷新、重连后刷新等功能,这些在 Promise 方案下需要额外的 visibilitychange 和网络事件监听来实现。

策略化的三个技术特征

1. 关注点分离

策略 Hook 将"请求的执行逻辑"与"业务组件"解耦。usePagination 内部处理翻页状态、数据聚合、缓存策略,组件只关心数据和渲染。这种分层让组件的单测和逻辑的单测可以独立进行。

2. 框架无关

alova 通过 StatesHook 适配层支持 React、Vue、Svelte、uniapp 等框架。同一套 usePagination / useForm API 在不同框架中接口一致,降低跨技术栈的认知成本。

3. 可组合性

策略之间可以组合使用。例如,usePagination 的数据可以通过 actionDelegationMiddleware 在其他组件中触发刷新,useForm 提交成功后可以调用 useFetcher 预加载下一页数据。

Trade-off 分析

策略化不是银弹。任何抽象都是一把双刃剑,带来的便利性背后,也有一些需要权衡的方面。

适用场景

  • 标准 CRUD 页面:列表、表单、详情等模式固定的场景,策略 Hook 可以直接覆盖大部分数据获取需求
  • 中大型项目:页面多、请求模式复杂、团队协作频繁的项目,统一的策略 API 有助于保持代码一致性,降低 review 成本
  • 多平台项目:需要同时支持 Web、小程序、移动端的项目,框架无关的策略层可以减少适配工作量
  • 需要缓存策略的场景:涉及 L1(内存)+ L2(持久化)缓存、自动失效、请求去重等复杂需求的场景,策略层内置的缓存管理可以减少手动实现的错误概率

局限 / 不适用场景

  • 极简单的页面:只有一个 GET 请求的独立页面,引入策略层的抽象成本可能高于收益;直接使用 fetch 或 axios 更轻量
  • 高度定制化的请求流程:如果业务的请求模式与内置策略差异很大,自定义实现可能更灵活(alova 支持自定义策略 Hook,但需要额外开发)
  • 已有成熟方案的项目:如果项目已基于 React Query 或 TanStack Query 深度集成且运行稳定,整体迁移的 ROI 不高;渐进式引入新模块时尝试策略 Hook 是更务实的做法
  • 需要直接操作底层 HTTP 的场景:如 WebSocket 连接管理、流式上传、Server-Sent Events 等场景,可能需要混合使用底层 API 和策略 Hook
  • 团队不熟悉策略化模式:策略化的概念需要团队理解和接受,对于习惯传统 Promise 写法的团队有一定的学习曲线

总结

从 Promise 到策略化,本质上是前端数据获取从"过程式编程"到"意图式编程"的一次范式转移。它不替代 Promise,而是在 Promise 之上提供了面向常见场景的语义化抽象。

这种进化路径与状态管理领域中 Redux → Recoil / Zustand 的演进有相似之处------不是"哪一种更好",而是"在不同场景下哪种抽象层级更合适"。

对于大多数业务开发场景来说,用几行代码声明意图,替代几十行样板代码来实现它,这种抽象是有实际价值的。但在简单场景下,过度抽象同样会带来不必要的复杂度。选择何种方案,取决于项目的规模、团队的熟悉程度和具体的业务需求。


标签:alova、前端请求库、请求策略、Promise、数据获取

作者简介:前端工程师,专注于前端工程化和数据层架构设计,关注请求库、状态管理和跨平台开发方向。

相关推荐
hsg7736 分钟前
简述:Jensen Huang‘s Footsteps网站全内容分析
前端·javascript·数据库
珑墨1 小时前
前端 AI 开发通用skill
前端
kyriewen1 小时前
一个人+Cursor,7天上线付费小程序:第1天我就想放弃了
前端·微信小程序·cursor
大家的林语冰1 小时前
Angular 王者归来,第 22 个主版本亮相,一大波前沿技术再度引领潮流!
前端·javascript·前端框架
老毛肚1 小时前
jeecgboot TS + Vue 模板化 03
前端·javascript·vue.js
下北沢美食家1 小时前
SSE 入门
前端
云计算磊哥@1 小时前
运维开发宝典023-WEB网站服务
运维·前端·运维开发
加点油。。。。2 小时前
【1.Obsidian渲染html文件】
前端·html·obsidian
ZFSS2 小时前
BYOK(自带密钥)使用指南
运维·服务器·前端·人工智能·midjourney