前言
本文分为两大模块:第一部分讲清 AI Agent 和普通对话大模型的本质区别、ReAct 循环与工具调用核心逻辑;第二部分围绕浏览器异步请求代码,逐行拆解 Promise、async、await、.then() 的底层运行逻辑与并发特性,完整代码放置在代码解析模块末尾。
一、AI Agent 底层原理:和普通对话 AI 的根本差异
1. 普通对话 LLM:单次单向问答流程
日常使用的普通对话 AI,执行链路是一次性闭环:用户输入指令 → 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 是 JS 中专门处理异步操作(网络请求、定时器、文件读取)的标准化对象,诞生目的是解决多层嵌套回调形成的「回调地狱」问题。
-
三种不可逆状态:
- pending:初始等待状态,异步操作未执行完成
- fulfilled:异步执行成功,状态锁定,触发
.then()回调逻辑 - rejected:异步执行失败,状态锁定,触发
.catch()回调逻辑
-
状态一旦切换为 fulfilled /rejected,就永久固定,无法再次修改;
-
浏览器原生接口
fetch,发起网络请求后返回的值本身就是一个 Promise 对象。
2. async 关键字详解:修饰函数,自动包装返回 Promise
代码中定义了两个请求函数:
javascript
运行
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
运行
ini
// const story = await getStory();
// const ratp = await getRatp();
await存在使用限制:只能写在被async修饰的函数内部,单独在全局使用会直接抛出语法错误;await核心作用:暂停当前函数内后续代码执行,等待后方 Promise 执行完成并拿到 resolved 结果后,才会继续执行下一行代码;- 这段注释代码属于串行请求:先发起一言接口请求,必须等待接口完整响应结束,才会发起必应壁纸接口请求,总耗时 = 接口 1 耗时 + 接口 2 耗时;
- 和下文
Promise.all并行写法形成明显性能差异,无依赖接口推荐并行执行。
4. Promise.all:并行批量处理多个 Promise(本节核心重点)
javascript
运行
scss
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
运行
ini
.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
运行
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
运行
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
预览
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</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>
三、全文总结
- AI Agent 的核心竞争力不在于对话能力,而是 ReAct 循环自主思考 + 工具调用;普通 LLM 仅能单次应答,无法自主迭代完成复杂任务,可用工具集合直接决定 Agent 的能力上限。
async用于修饰函数,让函数天然返回 Promise;await用于阻塞等待异步结果,多用于存在数据依赖的串行异步逻辑。Promise.all实现无依赖异步任务并发,搭配多层.then()链式处理分层异步逻辑,.catch()统一捕获整条链路的所有异常。- fetch 会返回原生 Promise 对象,接口响应后的
.json()解析操作依旧属于异步 Promise,批量解析多接口 JSON 需要二次使用 Promise.all 并行处理。