中文资讯 掘金 AI Wed, 15 Jul 2026 正文 ok 1138 words

⚡ Promise.all 性能优化:让 Agent 的工具调用飞起来

英文标题:⚡ Promise.all 性能优化:让 Agent 的工具调用飞起来

本文以 Agent 工具调用场景为例,讲解如何利用 Promise.all 并行执行多个工具调用,将总耗时从串行的 6 秒降至 2 秒。文章从 Promise 基础回顾开始,对比串行与并行执行,深入介绍 Promise.all、Promise.allSettled 和 Promise.race 的区别与适用场景,并给出完整的错误处理方案和手写 Promise.all 的实现。文中还提供了 Agent 场景下的性能对比与代码示例,帮助开发者理解异步机制,显著提升 AI Agent 的响应速度。
打开原文相关 4 · 机会 1
media3.2 KB media589 B media6.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);  
                });
        });
    });
}

核心逻辑

  1. 创建一个结果数组 results
  2. 遍历每个 Promise,监听它的完成
  3. 完成时把结果放到对应的索引位置(保持顺序)
  4. 全部完成后 resolve
  5. 任何一个 reject,立即 reject

💡 重点总结

  1. 串行 vs 并行 :串行是"一个一个等",并行是"同时开始,等最慢的"。

  2. Promise.all :全部成功才成功,一个失败就失败。结果顺序和传入顺序一致。

  3. Promise.allSettled :不管成败,全部返回。适合 Agent 场景(某个工具失败不影响其他)。

  4. Promise.race :谁先完成用谁。适合超时控制、多源竞速。

  5. Agent 性能优化 :多个独立的工具调用用 Promise.all 并行执行,可以成倍提升速度。

  6. 错误处理 :用 try-catch 包裹每个工具调用,把错误信息返回给 LLM,让它自己调整策略。


🔗 参考资料


💬 交流讨论

这是 LangChain Agent 系列的第四篇(完结篇)。四篇文章从概念到实战,完整覆盖了 Agent 开发的核心知识:

  1. LangChain 入门 —— Agent 的本质和 LangChain.js 基础
  2. Tool Calling —— 工具定义、绑定、调用的底层原理
  3. ReAct 循环 —— 自主规划、分步执行的实现
  4. 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);  
                });
        });
    });
}

核心逻辑

  1. 创建一个结果数组 results
  2. 遍历每个 Promise,监听它的完成
  3. 完成时把结果放到对应的索引位置(保持顺序)
  4. 全部完成后 resolve
  5. 任何一个 reject,立即 reject

💡 重点总结

  1. 串行 vs 并行 :串行是"一个一个等",并行是"同时开始,等最慢的"。

  2. Promise.all :全部成功才成功,一个失败就失败。结果顺序和传入顺序一致。

  3. Promise.allSettled :不管成败,全部返回。适合 Agent 场景(某个工具失败不影响其他)。

  4. Promise.race :谁先完成用谁。适合超时控制、多源竞速。

  5. Agent 性能优化 :多个独立的工具调用用 Promise.all 并行执行,可以成倍提升速度。

  6. 错误处理 :用 try-catch 包裹每个工具调用,把错误信息返回给 LLM,让它自己调整策略。


🔗 参考资料


💬 交流讨论

这是 LangChain Agent 系列的第四篇(完结篇)。四篇文章从概念到实战,完整覆盖了 Agent 开发的核心知识:

  1. LangChain 入门 —— Agent 的本质和 LangChain.js 基础
  2. Tool Calling —— 工具定义、绑定、调用的底层原理
  3. ReAct 循环 —— 自主规划、分步执行的实现
  4. Promise.all —— 工具调用的性能优化

接下来计划写 RAG(检索增强生成) —— 让 Agent 能查询私有知识库,敬请期待 👀


觉得有用?点个赞👍收藏⭐关注👆,后续带你搞定 RAG + Memory,让 Agent 真正"活"起来!


📎 标签 :Promise、性能优化、AI Agent、JavaScript、LangChain