Skip to content

超时与取消:如何利用 AbortSignal 实现请求中断?

在现代 Web 应用和后端服务中,能够控制和取消长时间运行的操作是一个重要的功能。LangChain V3 通过集成标准的 AbortSignal 机制,为开发者提供了强大的超时和取消能力。本章将深入探讨如何在 LangChain V3 中使用 AbortSignal 实现请求中断。

AbortSignal 简介

AbortSignal 是 Web 标准的一部分,用于与可取消的异步操作通信。它允许开发者在操作完成之前中止操作,这对于处理超时、用户取消操作或资源管理非常有用。

typescript
// 创建一个 AbortController
const controller = new AbortController();
const signal = controller.signal;

// 在某个异步操作中使用 signal
fetch('/api/data', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('请求已被取消');
    }
  });

// 在任何时候取消操作
controller.abort();

在 LangChain V3 中集成 AbortSignal

LangChain V3 通过 将 AbortSignal 集成到执行链中:

typescript
const controller = new AbortController();
const config: RunnableConfig = {
  signal: controller.signal
};

// 执行可能需要很长时间的操作
const result = await chain.invoke(input, config);

// 在另一个地方取消操作
controller.abort();

实现超时控制

通过 AbortSignal,我们可以轻松实现超时控制:

typescript
function withTimeout(runnable, timeoutMs) {
  return new RunnableProxy(runnable, {
    onInvoke: async (input, config = {}) => {
      // 创建带有超时的 AbortController
      const controller = new AbortController();
      const timeoutId = setTimeout(() => {
        controller.abort();
      }, timeoutMs);
      
      try {
        // 将 signal 添加到配置中
        const result = await runnable.invoke(input, {
          ...config,
          signal: controller.signal
        });
        return result;
      } finally {
        clearTimeout(timeoutId);
      }
    }
  });
}

// 使用示例
const timeoutChain = withTimeout(chain, 5000); // 5秒超时
try {
  const result = await timeoutChain.invoke(input);
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('操作超时');
  }
}

用户取消操作

在用户界面中,用户可能希望取消正在进行的操作:

typescript
class ChatApplication {
  private abortController: AbortController | null = null;
  
  async sendMessage(message) {
    // 取消之前的请求(如果有的话)
    if (this.abortController) {
      this.abortController.abort();
    }
    
    // 创建新的控制器
    this.abortController = new AbortController();
    
    try {
      const config: RunnableConfig = {
        signal: this.abortController.signal
      };
      
      const response = await chatChain.invoke(
        { message }, 
        config
      );
      
      return response;
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('请求被用户取消');
        return null;
      }
      throw error;
    } finally {
      this.abortController = null;
    }
  }
  
  cancelRequest() {
    if (this.abortController) {
      this.abortController.abort();
    }
  }
}

与现有超时机制的集成

LangChain V3 既支持通过 的属性设置超时,也支持通过 AbortSignal 实现更灵活的控制:

typescript
// 方法1: 使用 timeout 属性
const config1: RunnableConfig = {
  timeout: 5000 // 5秒超时
};

// 方法2: 使用 AbortSignal
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
const config2: RunnableConfig = {
  signal: controller.signal
};

// 方法3: 结合使用
const controller = new AbortController();
const config3: RunnableConfig = {
  timeout: 5000,
  signal: controller.signal
};

在底层 HTTP 客户端中的应用

LangChain V3 中的 LLM 集成通常会使用底层的 HTTP 客户端,这些客户端也支持 AbortSignal

typescript
class OpenAIChatModel extends BaseLanguageModel {
  async _call(prompt, options) {
    const { signal } = options;
    
    const response = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        model: this.modelName,
        messages: [{ role: 'user', content: prompt }]
      }),
      signal // 将 signal 传递给 fetch
    });
    
    const data = await response.json();
    return data.choices[0].message.content;
  }
}

批量操作中的取消

在批量操作中,AbortSignal 同样适用:

typescript
const controller = new AbortController();
const config: RunnableConfig = {
  signal: controller.signal
};

// 批量处理
const results = await chain.batch(inputs, config);

// 可以在任何时候取消整个批量操作
controller.abort();

错误处理和清理

使用 AbortSignal 时,需要注意适当的错误处理和资源清理:

typescript
class CancellableRunnable extends Runnable {
  async invoke(input, config = {}) {
    const { signal } = config;
    
    // 检查是否已经取消
    if (signal?.aborted) {
      throw new Error('操作已被取消');
    }
    
    // 监听取消事件
    return new Promise((resolve, reject) => {
      const onAbort = () => {
        cleanup(); // 清理资源
        reject(new Error('操作已被取消'));
      };
      
      signal?.addEventListener('abort', onAbort);
      
      // 执行实际操作
      this.performOperation(input)
        .then(result => {
          signal?.removeEventListener('abort', onAbort);
          resolve(result);
        })
        .catch(error => {
          signal?.removeEventListener('abort', onAbort);
          reject(error);
        });
    });
  }
  
  private performOperation(input) {
    // 实际的执行逻辑
  }
  
  private cleanup() {
    // 清理资源,如关闭连接、删除临时文件等
  }
}

实际应用示例

下面是一个完整的实际应用示例,展示了如何在聊天应用中使用 AbortSignal

typescript
class ChatBot {
  private currentRequestController: AbortController | null = null;
  
  async processUserMessage(userMessage: string) {
    // 取消之前的请求
    this.cancelCurrentRequest();
    
    // 创建新的控制器
    this.currentRequestController = new AbortController();
    
    try {
      // 构建聊天历史
      const chatHistory = this.getChatHistory();
      
      // 创建配置
      const config: RunnableConfig = {
        signal: this.currentRequestController.signal,
        metadata: {
          userId: this.currentUserId,
          timestamp: Date.now()
        }
      };
      
      // 执行聊天链
      const response = await this.chatChain.invoke({
        history: chatHistory,
        input: userMessage
      }, config);
      
      return response;
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('请求被取消');
        return "请求已被取消";
      }
      throw error;
    } finally {
      this.currentRequestController = null;
    }
  }
  
  cancelCurrentRequest() {
    if (this.currentRequestController) {
      this.currentRequestController.abort();
      this.currentRequestController = null;
    }
  }
}

总结

通过集成标准的 AbortSignal 机制,LangChain V3 为开发者提供了强大而灵活的超时和取消功能。这种设计不仅符合现代 Web 标准,还为构建响应式和用户友好的 LLM 应用提供了坚实的基础。

关键要点包括:

  1. AbortSignal 是 Web 标准,LangChain V3 通过 集成它
  2. 可以实现超时控制、用户取消操作等场景
  3. 与底层 HTTP 客户端无缝集成
  4. 支持单次操作和批量操作
  5. 需要适当的错误处理和资源清理

在下一章中,我们将深入探讨 LCEL 的本质,了解函数式管道与范畴论的实践。