@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 说明也明确区分了客户端取消与服务端操作取消。
备注:内容仅供参考。
