ReAct 智能体循环与 JavaScript 异步并发原理深度剖析
本文系统解析两大核心主题:AI Agent与普通对话大模型在ReAct循环上的本质差异,以及JavaScript异步编程中Promise、async、await、.then的底层运行逻辑与并发特性。
前言
一、AI Agent 底层原理:和普通对话 AI 的根本差异
1. 普通对话 LLM:单次单向问答流程
日常使用的普通对话大模型执行链路为一次性闭环:用户输入指令后,LLM基于训练静态知识生成文本并直接输出,任务即刻终止。此过程不涉及任何自主循环逻辑,也无法主动操作外部资源。以竞品分析报告为例,普通AI仅能依赖训练时积累的旧知识生成文字,无法主动联网查询最新动态或抓取财务数据,输出完成后流程完结,缺乏自我校验与信息补充能力。
2. AI Agent:基于 ReAct 的循环自主执行架构
Agent的核心特征在于可持续循环的执行链路,其标准流程称为ReAct,由三步动作循环往复运行,直至任务完成或触发终止条件:
- Reason(思考):大模型自主分解复杂任务,识别当前信息缺口,规划下一步执行动作。
- Act(行动):调用外部工具执行操作,包括联网搜索、代码运行、浏览器访问、API请求等。
- Observe(观察):接收工具返回的执行结果,判断现有信息是否足以完成目标任务。
一轮循环结束后,自动返回“思考”步骤,持续迭代直至任务终结。
场景示例:Agent 完成竞品分析报告完整循环
- 思考:缺少竞品实时业务数据,需调用搜索工具;行动:发起联网搜索;观察:获取竞品基础动态,识别尚缺财务报表数据。
- 再次思考:需抓取股市财务接口补充数据;行动:请求财务API;观察:所需信息收集完毕,无需继续调用工具。
- 终止循环,整合所有外部数据输出完整竞品报告。
3. Agent 的核心能力:Tool Use 工具调用
工具是Agent连接现实世界的唯一载体,缺乏工具的Agent与普通对话LLM无异,仅能输出纯文本。可用的工具集合直接界定Agent的能力边界:
- 联网搜索工具:获取互联网实时动态与最新资讯。
- 代码执行沙盒:运行代码、批量数据计算、自动化测试。
- 文件读写工具:本地文档生成、内容修改、文件导出。
- 浏览器自动化工具:模拟人工操作网页、抓取实时数据。
- 第三方API调用:对接企业内部系统与各类业务服务接口。
需要补充的是,ReAct仅是一套通用执行标准,并非LangChain这类开发框架,当前所有Agent开发框架底层均基于此循环逻辑封装实现。
二、JS 异步代码深度解析(重点 Promise /async/await /.then)
1. 基础概念铺垫:Promise 核心规则
Promise是JavaScript中专门处理异步操作(如网络请求、定时器、文件读取)的标准化对象,其设计初衷在于解决多层嵌套回调形成的“回调地狱”问题。
- 三种不可逆状态:
- pending:初始等待状态,异步操作尚未完成。
- fulfilled:异步执行成功,状态锁定,触发
.then()回调逻辑。 - rejected:异步执行失败,状态锁定,触发
.catch()回调逻辑。
- 状态一旦切换为fulfilled或rejected,便永久固定,无法再次修改。
- 浏览器原生接口
fetch,发起网络请求后返回的值本身即一个Promise对象。
2. async 关键字详解:修饰函数,自动包装返回 Promise
代码中定义了两个请求函数:
javascript
运行
const getStory = async () => fetch('https://v1.hitokoto.cn/?c=i&encode=json')const getRatp = async () => fetch('https://api.1314.cool/bingimg/?type=json&rand=1')
async仅用于修饰函数,放置在函数声明或箭头函数前方。- 无论函数内部返回何种类型值,
async都会自动将返回值包装为Promise:- 若函数返回普通常量如
return 123,等价于Promise.resolve(123)。 - 若函数内部抛出错误,等价于
Promise.reject(错误信息)。
- 若函数返回普通常量如
- 示例中
fetch本身就返回Promise,外层叠加async修饰后,调用getStory()与getRatp()依然得到独立Promise对象,对应两个互不依赖的网络请求。
3. await 关键字:阻塞等待 Promise 完成(注释串行代码对比)
源码中注释了一段串行请求写法:
javascript
运行
// const story = await getStory();// const ratp = await getRatp();
await存在使用限制:只能写在被async修饰的函数内部,单独在全局使用会抛出语法错误。await核心作用:暂停当前函数内后续代码执行,等待后方Promise执行完成并拿到resolved结果后,才继续执行下一行。- 这段注释代码属于串行请求:先发起一言接口请求,必须待第一个接口完全响应后,才发起必应壁纸请求,总耗时等于接口1耗时加接口2耗时。
- 与下文
Promise.all并行写法形成明显性能差异,无依赖接口推荐使用并行执行。
4. Promise.all:并行批量处理多个 Promise(本节核心重点)
javascript
运行
Promise.all([getStory(), getRatp()])
- 入参要求:接收由多个Promise实例组成的数组。
- 执行逻辑:数组内所有Promise同步并发发起,两个网络请求同时发送,总耗时等于耗时最长的接口执行时间,性能远优于串行
await。 - 返回规则:
- 仅当数组内全部Promise都执行成功(fulfilled),才进入后续
.then()回调。 - 返回结果为数组,元素顺序与传入Promise数组顺序严格对应,与接口响应快慢无关。例如壁纸接口100ms返回、一言接口500ms返回,最终结果数组依然保持
[一言结果, 壁纸结果]的顺序。
- 仅当数组内全部Promise都执行成功(fulfilled),才进入后续
- 标志性特性“快速失败”:数组中任意一个Promise执行失败(如404、网络中断、请求报错),整个
Promise.all会立即终止,直接跳转.catch(),丢弃其余未完成Promise的返回结果。
5. .then () 链式回调:接收成功结果,返回新 Promise
javascript
运行
.then(response => {return Promise.all(response.map(res => res.json()))})
.then()的入参是上一层Promise.all成功返回的数组,其中存储两个fetch得到的Response响应对象。res.json()是Response对象自带的异步方法,作用是将接口返回的JSON字符串解析为JS对象,该方法自身仍返回Promise。- 通过
response.map遍历所有Response对象,批量调用.json()生成一组全新Promise,再依靠Promise.all并行完成所有JSON解析操作。 .then内部若存在return语句,返回值会自动包装为Promise对象,传递给下一层链式回调,这是Promise链式调用的底层基础。
6. .catch () 统一捕获全链路异步错误
javascript
运行
.catch(response => {console.log(response)})
.catch()能捕获整条Promise链路中任意位置的失败,包括第一层Promise.all接口请求失败、第二层res.json()解析格式报错等所有异常。- 结合
Promise.all的快速失败特性:只要任意一步异步操作抛出错误,代码直接跳转到当前catch,不会执行后续任何.then逻辑。 - 写法对比:若使用
async/await串行写法,需搭配try/catch捕获异常,而.then/.catch链式写法可集中统一处理全链路异常。
7. async 主函数 main 与程序执行入口
javascript
运行
async function main() {Promise.all([getStory(), getRatp()]).then(response => {return Promise.all(response.map(res => res.json()))}).catch(response => {console.log(response)})}main();
- 函数
main被async修饰,理论内部可使用await,但本次代码完全依赖Promise静态方法实现并发,未使用await。 - 末尾手动调用
main(),页面加载后自动触发整套异步请求逻辑,一次性发起两个网络接口调用。
8. 串行 await 和 Promise.all 并行核心区别总结
- 串行(注释
await代码):接口按顺序执行,总耗时叠加,适用于接口间存在数据依赖(如第二个接口需第一个接口返回参数)。 - 并行(
Promise.all):同步发起所有无依赖接口,总耗时取最长接口耗时,适合多独立接口批量请求,可大幅优化页面加载速度。 - 风险提示:
Promise.all存在快速失败特性,若业务场景需要等待所有请求完成、无论成功失败都保留全部结果,可替换为Promise.allSettled。
完整可运行代码(放置本模块末尾)
html
预览
><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Documenttitle>head><body><script>// const streetCarsPromise = fetch('http://data.ratp.fr/api/datasets/1.0/search/?q=paris');// console.log(streetCarsPromise);const getStory = async () => fetch('https://v1.hitokoto.cn/?c=i&encode=json')const getRatp = async () => fetch('https://api.1314.cool/bingimg/?type=json&rand=1')async function main() {// 不存在依赖关系 , 并行以下 Promise 一系列静态方法// const story = await getStory();// const ratp = await getRatp();// 并发的效果 // 参数是promise 数组// 不管谁先解决,谁后, resolve 的结果会按promise 数组// 顺序来收集。最后一个promise resolved 之后, 停下来 Promise.all([getStory(), getRatp()]).then(response => {return Promise.all(response.map(res => res.json()))}).catch(response => {console.log(response)}) }main();script>body>html>
三、全文总结
核心要点归纳如下:1. AI Agent的核心竞争力在于ReAct循环自主思考与工具调用能力,而非单次对话;普通LLM仅能单次应答,无法自主迭代完成复杂任务,可用工具集合决定Agent的能力上限。2. async用于修饰函数,使其天然返回Promise;await用于阻塞等待异步结果,适用于存在数据依赖的串行逻辑。3. Promise.all实现无依赖异步任务并发,搭配多层.then()链式处理分层逻辑,.catch()统一捕获整条链路异常。4. fetch返回原生Promise对象,接口响应后的.json()解析仍属异步操作,批量解析多接口JSON需二次使用Promise.all并行处理。