⚡ Promise.all 性能优化:让 Agent 的工具调用飞起来
英文标题:⚡ Promise.all 性能优化:让 Agent 的工具调用飞起来
本文以 Agent 工具调用场景为例,讲解如何利用 Promise.all 并行执行多个工具调用,将总耗时从串行的 6 秒降至 2 秒。文章从 Promise 基础回顾开始,对比串行与并行执行,深入介绍 Promise.all、Promise.allSettled 和 Promise.race 的区别与适用场景,并给出完整的错误处理方案和手写 Promise.all 的实现。文中还提供了 Agent 场景下的性能对比与代码示例,帮助开发者理解异步机制,显著提升 AI Agent 的响应速度。
6.4 KB
中文
⚡ Promise.all 性能优化:让 Agent 的工具调用飞起来
摘要 :Agent 一次要读 3 个文件,串行要 6 秒,并行只要 2 秒。本文从 Promise 基础讲起,手把手教你用 Promise.all 优化 Agent 的工具调用性能,附带完整的错误处理方案。
📌 前言
上一篇我们实现了 ReAct 循环,Agent 能自主规划、分步执行任务了。
但有一个问题:当 LLM 一次返回多个 tool_calls 时,我们是串行执行的。
for (const toolCall of response.tool_calls) {
const result = await tool.invoke(toolCall.args);
}
如果 LLM 一次要读 3 个文件,每个读取需要 2 秒,串行就是 6 秒。
用 Promise.all 并行执行,只需要 2 秒。
🎯 本文适合谁
- 对 Promise 了解但不深入的同学
- 想优化 Agent 工具调用性能的开发者
- 想搞懂 Promise.all / Promise.allSettled / Promise.race 区别的同学
📚 一、Promise 基础回顾
什么是 Promise?
Promise 是 ES6 提供的异步编程解决方案。它代表一个未来才会完成的操作 。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('完成!');
}, 1000);
});
Promise 的三个状态
┌───────────┐
│ Pending │ ← 初始状态
│ 等待中... │
└─────┬─────┘
│
├──── resolve() ──→ ┌───────────┐
│ │ Fulfilled │
│ │ 已完成 ✅ │
│ └───────────┘
│
└──── reject() ──→ ┌───────────┐
│ Rejected │
│ 已拒绝 ❌ │
└───────────┘
关键规则 :
- 状态只能从
Pending变为Fulfilled或Rejected之一 - 一旦变化,不可逆转
async / await:Promise 的语法糖
function getData() {
return fetch('/api/data')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
}
async function getData() {
try {
const res = await fetch('/api/data');
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
}
💡
await会暂停当前函数的执行,等 Promise 完成后再继续。它把异步代码写成了同步的风格,可读性大大提升。
📚 二、串行 vs 并行
场景模拟
假设我们要同时获取天气和推文数据:
function getWeather() {
return new Promise((resolve) => {
setTimeout(() => resolve({ temp: 38, conditions: '晴转多云' }), 2000);
});
}
function getTweets() {
return new Promise((resolve) => {
setTimeout(() => resolve(['今天天气真好', '周末去爬山']), 500);
});
}
❌ 串行执行
async function serial() {
console.time('串行');
const weather = await getWeather();
const tweets = await getTweets();
console.log(weather, tweets);
console.timeEnd('串行');
}
时间线:
0ms ──────────── 2000ms ──── 2500ms
|──── getWeather ────|─ getTweets ─|
总耗时:2500ms
✅ 并行执行
async function parallel() {
console.time('并行');
const [weather, tweets] = await Promise.all([getWeather(), getTweets()]);
console.log(weather, tweets);
console.timeEnd('并行');
}
时间线:
0ms ──────────── 2000ms
|──── getWeather ────|
|─ getTweets ─|
总耗时:2000ms(取最慢的那个)
节省了 500ms! 如果任务更多、耗时差距更大,优化效果会更明显。
📚 三、Promise.all 详解
基本用法
const results = await Promise.all([promise1, promise2, promise3]);
核心特性
| 特性 | 说明 |
|---|---|
| 并行执行 | 所有 Promise 同时开始执行 |
| 全部成功才成功 | 所有 Promise 都 resolve 后,才返回结果数组 |
| 一个失败就失败 | 任意一个 reject,Promise.all 立即 reject |
| 结果有序 | 返回数组的顺序和传入 Promise 的顺序一致 |
结果顺序
const p1 = new Promise(resolve => setTimeout(() => resolve('第一个'), 3000));
const p2 = new Promise(resolve => setTimeout(() => resolve('第二个'), 1000));
const p3 = new Promise(resolve => setTimeout(() => resolve('第三个'), 2000));
const results = await Promise.all([p1, p2, p3]);
console.log(results);
错误处理
const p1 = Promise.resolve('成功');
const p2 = Promise.reject('失败');
const p3 = Promise.resolve('成功');
try {
const results = await Promise.all([p1, p2, p3]);
} catch (err) {
console.log(err);
}
📚 四、Promise.all 的替代方案
Promise.allSettled:不管成败,全部返回
const results = await Promise.allSettled([
Promise.resolve('成功'),
Promise.reject('失败'),
Promise.resolve('成功'),
]);
console.log(results);
适用场景 :Agent 工具调用 —— 某个工具失败了,其他工具的结果仍然需要。
Promise.race:谁先完成用谁
const result = await Promise.race([
fetch('https://api1.example.com/data'),
fetch('https://api2.example.com/data'),
new Promise((_, reject) => setTimeout(() => reject('超时'), 5000)),
]);
适用场景 :多源数据竞速、超时控制。
对比总结
| 方法 | 成功条件 | 失败条件 | 返回值 |
|---|---|---|---|
Promise.all |
全部成功 | 任一失败 | 结果数组 |
Promise.allSettled |
不关心 | 不关心 | 状态+结果数组 |
Promise.race |
第一个完成 | 第一个完成 | 单个结果 |
📚 五、在 Agent 中应用 Promise.all
串行版本(慢)
for (const toolCall of response.tool_calls) {
const tool = tools.find(t => t.name === toolCall.name);
if (tool) {
const result = await tool.invoke(toolCall.args);
messages.push(new ToolMessage({
content: result,
tool_call_id: toolCall.id,
}));
}
}
并行版本(快)
const toolResults = await Promise.all(
response.tool_calls.map(async (toolCall) => {
const tool = tools.find(t => t.name === toolCall.name);
if (!tool) return `工具 ${toolCall.name} 不存在`;
try {
return await tool.invoke(toolCall.args);
} catch (error) {
return `工具调用失败:${error.message}`;
}
})
);
response.tool_calls.forEach((toolCall, index) => {
messages.push(new ToolMessage({
tool_call_id: toolCall.id,
content: toolResults[index]
}));
});
性能对比
假设 LLM 一次返回 3 个 tool_calls,每个工具执行需要 2 秒:
| 方案 | 执行方式 | 总耗时 |
|---|---|---|
| 串行 | 2s + 2s + 2s | 6 秒 |
| 并行 | max(2s, 2s, 2s) | 2 秒 |
提速 3 倍! 如果工具调用更多,差距会更大。
📚 六、进阶:Promise.allSettled 在 Agent 中的应用
在 Agent 场景中,某个工具调用失败不应该影响其他工具。这时候用 Promise.allSettled 更合适:
const toolResults = await Promise.allSettled(
response.tool_calls.map(async (toolCall) => {
const tool = tools.find(t => t.name === toolCall.name);
if (!tool) throw new Error(`工具 ${toolCall.name} 不存在`);
return await tool.invoke(toolCall.args);
})
);
response.tool_calls.forEach((toolCall, index) => {
const result = toolResults[index];
const content = result.status === 'fulfilled'
? result.value
: `工具调用失败:${result.reason}`;
messages.push(new ToolMessage({
tool_call_id: toolCall.id,
content: content,
}));
});
优势 :即使某个工具失败了,其他工具的结果仍然能正常返回给 LLM。
📚 七、手写一个 Promise.all
理解原理最好的方式是自己实现一遍:
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completedCount = 0;
const total = promises.length;
if (total === 0) {
resolve([]);
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((result) => {
results[index] = result;
completedCount++;
if (completedCount === total) {
resolve(results);
}
})
.catch((err) => {
reject(err);
});
});
});
}
核心逻辑
- 创建一个结果数组
results - 遍历每个 Promise,监听它的完成
- 完成时把结果放到对应的索引位置(保持顺序)
- 全部完成后 resolve
- 任何一个 reject,立即 reject
💡 重点总结
-
串行 vs 并行 :串行是"一个一个等",并行是"同时开始,等最慢的"。
-
Promise.all :全部成功才成功,一个失败就失败。结果顺序和传入顺序一致。
-
Promise.allSettled :不管成败,全部返回。适合 Agent 场景(某个工具失败不影响其他)。
-
Promise.race :谁先完成用谁。适合超时控制、多源竞速。
-
Agent 性能优化 :多个独立的工具调用用 Promise.all 并行执行,可以成倍提升速度。
-
错误处理 :用 try-catch 包裹每个工具调用,把错误信息返回给 LLM,让它自己调整策略。
🔗 参考资料
💬 交流讨论
这是 LangChain Agent 系列的第四篇(完结篇)。四篇文章从概念到实战,完整覆盖了 Agent 开发的核心知识:
- LangChain 入门 —— Agent 的本质和 LangChain.js 基础
- Tool Calling —— 工具定义、绑定、调用的底层原理
- ReAct 循环 —— 自主规划、分步执行的实现
- Promise.all —— 工具调用的性能优化
接下来计划写 RAG(检索增强生成) —— 让 Agent 能查询私有知识库,敬请期待 👀
觉得有用?点个赞👍收藏⭐关注👆,后续带你搞定 RAG + Memory,让 Agent 真正"活"起来!
📎 标签 :
Promise、性能优化、AI Agent、JavaScript、LangChain
English / 原文
⚡ Promise.all 性能优化:让 Agent 的工具调用飞起来
摘要 :Agent 一次要读 3 个文件,串行要 6 秒,并行只要 2 秒。本文从 Promise 基础讲起,手把手教你用 Promise.all 优化 Agent 的工具调用性能,附带完整的错误处理方案。
📌 前言
上一篇我们实现了 ReAct 循环,Agent 能自主规划、分步执行任务了。
但有一个问题:当 LLM 一次返回多个 tool_calls 时,我们是串行执行的。
for (const toolCall of response.tool_calls) {
const result = await tool.invoke(toolCall.args);
}
如果 LLM 一次要读 3 个文件,每个读取需要 2 秒,串行就是 6 秒。
用 Promise.all 并行执行,只需要 2 秒。
🎯 本文适合谁
- 对 Promise 了解但不深入的同学
- 想优化 Agent 工具调用性能的开发者
- 想搞懂 Promise.all / Promise.allSettled / Promise.race 区别的同学
📚 一、Promise 基础回顾
什么是 Promise?
Promise 是 ES6 提供的异步编程解决方案。它代表一个未来才会完成的操作 。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('完成!');
}, 1000);
});
Promise 的三个状态
┌───────────┐
│ Pending │ ← 初始状态
│ 等待中... │
└─────┬─────┘
│
├──── resolve() ──→ ┌───────────┐
│ │ Fulfilled │
│ │ 已完成 ✅ │
│ └───────────┘
│
└──── reject() ──→ ┌───────────┐
│ Rejected │
│ 已拒绝 ❌ │
└───────────┘
关键规则 :
- 状态只能从
Pending变为Fulfilled或Rejected之一 - 一旦变化,不可逆转
async / await:Promise 的语法糖
function getData() {
return fetch('/api/data')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
}
async function getData() {
try {
const res = await fetch('/api/data');
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
}
💡
await会暂停当前函数的执行,等 Promise 完成后再继续。它把异步代码写成了同步的风格,可读性大大提升。
📚 二、串行 vs 并行
场景模拟
假设我们要同时获取天气和推文数据:
function getWeather() {
return new Promise((resolve) => {
setTimeout(() => resolve({ temp: 38, conditions: '晴转多云' }), 2000);
});
}
function getTweets() {
return new Promise((resolve) => {
setTimeout(() => resolve(['今天天气真好', '周末去爬山']), 500);
});
}
❌ 串行执行
async function serial() {
console.time('串行');
const weather = await getWeather();
const tweets = await getTweets();
console.log(weather, tweets);
console.timeEnd('串行');
}
时间线:
0ms ──────────── 2000ms ──── 2500ms
|──── getWeather ────|─ getTweets ─|
总耗时:2500ms
✅ 并行执行
async function parallel() {
console.time('并行');
const [weather, tweets] = await Promise.all([getWeather(), getTweets()]);
console.log(weather, tweets);
console.timeEnd('并行');
}
时间线:
0ms ──────────── 2000ms
|──── getWeather ────|
|─ getTweets ─|
总耗时:2000ms(取最慢的那个)
节省了 500ms! 如果任务更多、耗时差距更大,优化效果会更明显。
📚 三、Promise.all 详解
基本用法
const results = await Promise.all([promise1, promise2, promise3]);
核心特性
| 特性 | 说明 |
|---|---|
| 并行执行 | 所有 Promise 同时开始执行 |
| 全部成功才成功 | 所有 Promise 都 resolve 后,才返回结果数组 |
| 一个失败就失败 | 任意一个 reject,Promise.all 立即 reject |
| 结果有序 | 返回数组的顺序和传入 Promise 的顺序一致 |
结果顺序
const p1 = new Promise(resolve => setTimeout(() => resolve('第一个'), 3000));
const p2 = new Promise(resolve => setTimeout(() => resolve('第二个'), 1000));
const p3 = new Promise(resolve => setTimeout(() => resolve('第三个'), 2000));
const results = await Promise.all([p1, p2, p3]);
console.log(results);
错误处理
const p1 = Promise.resolve('成功');
const p2 = Promise.reject('失败');
const p3 = Promise.resolve('成功');
try {
const results = await Promise.all([p1, p2, p3]);
} catch (err) {
console.log(err);
}
📚 四、Promise.all 的替代方案
Promise.allSettled:不管成败,全部返回
const results = await Promise.allSettled([
Promise.resolve('成功'),
Promise.reject('失败'),
Promise.resolve('成功'),
]);
console.log(results);
适用场景 :Agent 工具调用 —— 某个工具失败了,其他工具的结果仍然需要。
Promise.race:谁先完成用谁
const result = await Promise.race([
fetch('https://api1.example.com/data'),
fetch('https://api2.example.com/data'),
new Promise((_, reject) => setTimeout(() => reject('超时'), 5000)),
]);
适用场景 :多源数据竞速、超时控制。
对比总结
| 方法 | 成功条件 | 失败条件 | 返回值 |
|---|---|---|---|
Promise.all |
全部成功 | 任一失败 | 结果数组 |
Promise.allSettled |
不关心 | 不关心 | 状态+结果数组 |
Promise.race |
第一个完成 | 第一个完成 | 单个结果 |
📚 五、在 Agent 中应用 Promise.all
串行版本(慢)
for (const toolCall of response.tool_calls) {
const tool = tools.find(t => t.name === toolCall.name);
if (tool) {
const result = await tool.invoke(toolCall.args);
messages.push(new ToolMessage({
content: result,
tool_call_id: toolCall.id,
}));
}
}
并行版本(快)
const toolResults = await Promise.all(
response.tool_calls.map(async (toolCall) => {
const tool = tools.find(t => t.name === toolCall.name);
if (!tool) return `工具 ${toolCall.name} 不存在`;
try {
return await tool.invoke(toolCall.args);
} catch (error) {
return `工具调用失败:${error.message}`;
}
})
);
response.tool_calls.forEach((toolCall, index) => {
messages.push(new ToolMessage({
tool_call_id: toolCall.id,
content: toolResults[index]
}));
});
性能对比
假设 LLM 一次返回 3 个 tool_calls,每个工具执行需要 2 秒:
| 方案 | 执行方式 | 总耗时 |
|---|---|---|
| 串行 | 2s + 2s + 2s | 6 秒 |
| 并行 | max(2s, 2s, 2s) | 2 秒 |
提速 3 倍! 如果工具调用更多,差距会更大。
📚 六、进阶:Promise.allSettled 在 Agent 中的应用
在 Agent 场景中,某个工具调用失败不应该影响其他工具。这时候用 Promise.allSettled 更合适:
const toolResults = await Promise.allSettled(
response.tool_calls.map(async (toolCall) => {
const tool = tools.find(t => t.name === toolCall.name);
if (!tool) throw new Error(`工具 ${toolCall.name} 不存在`);
return await tool.invoke(toolCall.args);
})
);
response.tool_calls.forEach((toolCall, index) => {
const result = toolResults[index];
const content = result.status === 'fulfilled'
? result.value
: `工具调用失败:${result.reason}`;
messages.push(new ToolMessage({
tool_call_id: toolCall.id,
content: content,
}));
});
优势 :即使某个工具失败了,其他工具的结果仍然能正常返回给 LLM。
📚 七、手写一个 Promise.all
理解原理最好的方式是自己实现一遍:
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completedCount = 0;
const total = promises.length;
if (total === 0) {
resolve([]);
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then((result) => {
results[index] = result;
completedCount++;
if (completedCount === total) {
resolve(results);
}
})
.catch((err) => {
reject(err);
});
});
});
}
核心逻辑
- 创建一个结果数组
results - 遍历每个 Promise,监听它的完成
- 完成时把结果放到对应的索引位置(保持顺序)
- 全部完成后 resolve
- 任何一个 reject,立即 reject
💡 重点总结
-
串行 vs 并行 :串行是"一个一个等",并行是"同时开始,等最慢的"。
-
Promise.all :全部成功才成功,一个失败就失败。结果顺序和传入顺序一致。
-
Promise.allSettled :不管成败,全部返回。适合 Agent 场景(某个工具失败不影响其他)。
-
Promise.race :谁先完成用谁。适合超时控制、多源竞速。
-
Agent 性能优化 :多个独立的工具调用用 Promise.all 并行执行,可以成倍提升速度。
-
错误处理 :用 try-catch 包裹每个工具调用,把错误信息返回给 LLM,让它自己调整策略。
🔗 参考资料
💬 交流讨论
这是 LangChain Agent 系列的第四篇(完结篇)。四篇文章从概念到实战,完整覆盖了 Agent 开发的核心知识:
- LangChain 入门 —— Agent 的本质和 LangChain.js 基础
- Tool Calling —— 工具定义、绑定、调用的底层原理
- ReAct 循环 —— 自主规划、分步执行的实现
- Promise.all —— 工具调用的性能优化
接下来计划写 RAG(检索增强生成) —— 让 Agent 能查询私有知识库,敬请期待 👀
觉得有用?点个赞👍收藏⭐关注👆,后续带你搞定 RAG + Memory,让 Agent 真正"活"起来!
📎 标签 :
Promise、性能优化、AI Agent、JavaScript、LangChain