PAYATHON 2026

@google/genai 的 ai.files.upload 为何忽略 abortSignal?

技术老齐

结论

config.abortSignal 的传入位置符合 SDK 接口定义。所查官方源码中的上传实现没有继续传递这个信号。如果你使用的版本采用相同实现,这就是上传取消功能的实现缺口,可以解释为什么调用 abort() 后上传仍然成功返回。问题没有提供 SDK 版本,因此无法断言所有版本都受影响,也无法指定某个已修复版本。

官方 UploadFileConfig 文档定义了 abortSignal。但在所查的官方 ApiClient.uploadFile 源码中,获取上传地址时传入的是 config?.httpOptions,后续调用为 uploader.upload(file, uploadUrl, this)。这条路径没有把 config.abortSignal 传给上传器,因此该实现没有接通调用方的取消信号。

为什么 aborted 为 true,却没有进入 catch?

AbortController 是一种协作式取消机制。调用 abort() 会更新对应信号的状态并触发取消事件,但不会自动终止任意异步函数,也不会强制所有相关 Promise 拒绝。

上传代码需要监听该信号,或将它传给支持取消的底层请求,取消才会生效。因此,下面两件事可以同时发生:

  • controller.signal.aborted === true:调用方已经发出取消信号。
  • ai.files.upload(...) 成功返回:上传实现没有响应信号,或者取消发生时操作已经完成。

只有当 try 内抛出异常或等待的 Promise 拒绝时,才会执行 catch。单独改变 signal.aborted 不会让执行流程自动跳入 catch。

如何确认自己的版本是否存在这个问题?

先确认项目实际安装的版本:

npm ls @google/genai

然后检查对应版本的源码或构建产物,沿着 files.upload → uploadFile → uploader.upload 查看取消信号是否传入底层请求。类型定义中有 abortSignal,并不能说明上传取消已经实现;GitHub 的 main 分支也不能代替项目当前安装的版本。

复现时,可以先传入一个已经取消的信号,减少点击时机带来的干扰:

const controller = new AbortController();
controller.abort();

try {
  const file = await ai.files.upload({
    file: new Blob(["fake audio bytes"], { type: "audio/mp3" }),
    config: {
      abortSignal: controller.signal
    }
  });

  console.log("Upload resolved:", file);
} catch (error) {
  console.log("Upload rejected:", error);
}

如果上传仍成功返回,说明这次调用没有因预先取消的信号而拒绝。再结合对应版本的实现,判断是否属于信号未传递的问题。测试上传期间的取消时,可以使用合适的测试文件,配合浏览器网络限速,观察请求是否实际中断。

临时处理:取消后忽略上传结果

如果当前版本不能真正中止上传,可以先确保用户取消后,上传结果不会继续写入页面。下面的代码可替换原示例中的两个函数,并新增 uploading 状态;其余 ai、result 和 abortController 声明继续沿用原示例。

const uploading = ref(false);

async function sendRequest() {
  if (uploading.value) return;

  const controller = new AbortController();
  abortController = controller;
  uploading.value = true;
  result.value = "";

  try {
    const mockAudioBlob = new Blob(["fake audio bytes"], {
      type: "audio/mp3"
    });

    const myFile = await ai.files.upload({
      file: mockAudioBlob,
      config: {
        abortSignal: controller.signal
      }
    });

    if (controller.signal.aborted) {
      console.log("Upload result ignored after cancellation");
      return;
    }

    result.value = myFile.uri;
  } catch (error) {
    if (error && error.name === "AbortError") {
      console.log("Request was aborted");
    } else {
      console.error(error);
    }
  } finally {
    if (abortController === controller) {
      abortController = null;
    }
    uploading.value = false;
  }
}

function abortRequest() {
  if (abortController && !abortController.signal.aborted) {
    abortController.abort();
    console.log("Cancellation requested");
  }
}

代码使用函数内部的 controller 检查本次上传,避免异步代码读取被其他调用替换的控制器。uploading 则用来阻止重复启动上传。

这个方案只会忽略取消后返回的结果。若 SDK 没有响应信号,底层传输仍可能继续,finally 也要等上传 Promise 结束才会执行。代码没有强行制造 AbortError,因此点击取消时不会立即运行 catch。

如果必须真正停止传输

需要使用已确认将取消信号接入上传链路的 SDK 实现,或者自行实现上传请求,将同一个 AbortSignal 传给各阶段的底层 fetch。自行实现时,需要按照文件上传协议处理获取上传地址、发送文件等步骤。

用 Promise.race 包装上传,可以让调用方提前结束等待,但无法单独停止仍在运行的网络请求。即使客户端请求成功中止,也不能保证服务端撤销已经接收的操作;官方 abortSignal 说明也明确区分了客户端取消与服务端操作取消。

备注:内容仅供参考。