Firebase Cloud Function使用uploadBytes报错及Storage模拟器问题
uploadBytes上传至Storage的问题 我在编写Firebase Cloud Function时,尝试用uploadBytes上传文件到Firebase Cloud Storage,按照官方文档操作,但一直抛出错误:
TypeError: Cannot read properties of undefined (reading 'byteLength')
这个错误不在官方错误处理文档里,推测是找不到待上传文件,模拟器和云端环境都存在该问题。
1. uploadString测试正常,但存在超时问题
测试uploadString可以正常工作,代码如下:
import { initializeApp } from "firebase/app"; import * as functions from "firebase-functions"; import { getStorage, ref, uploadBytes, uploadString, connectStorageEmulator } from "firebase/storage"; const firebaseConfig = { apiKey: "12345", authDomain: "my-awesome-app.firebaseapp.com", databaseURL: "https://my-awesome-app.firebaseio.com", projectId: "my-awesome-app", storageBucket: "my-awesome-app.appspot.com", messagingSenderId: "12345", appId: "12345" }; initializeApp(firebaseConfig); export const StringMe = functions.firestore.document('StringMe/{userID}').onUpdate((change, context) => { const storage = getStorage(); // const storageRef = ref(storage, 'message.txt'); // location to write to // connectStorageEmulator(storage, "localhost", 9199); // comment out to write to the cloud const storageRef = ref(storage, 'gs://my-awesome-app.appspot.com/Pictures/message.txt'); const message = "Hello world!"; async function uploadMessage() { try { await uploadString(storageRef, message); console.log("Uploaded a string!"); } catch (error) { console.error(error); } }; return uploadMessage() });
日志显示Uploaded a string!和Finished "StringMe" in 417.150521ms,但60秒后会抛出超时错误:
functions: Your function timed out after ~60s. To configure this timeout, see https://firebase.google.com/docs/functions/manage-functions#set_timeout_and_memory_allocation. ⚠ Your function was killed because it raised an unhandled error.
暂时认为是Firebase CLI的bug,先忽略。
2. Storage模拟器无输出也无写入
切换到模拟器时,注释原storageRef并启用以下两行:
const storageRef = ref(storage, 'message.txt'); connectStorageEmulator(storage, "localhost", 9199);
此时除了超时外没有其他报错,但没有日志输出,也没有内容写入Storage,Storage模拟器为何无法工作?
3. 上传自定义模块中的字符串仍触发byteLength错误
自定义模块my-module.js内容:
export const file = "Hello world";
对应的上传代码:
import { initializeApp } from "firebase/app"; import * as functions from "firebase-functions"; import { getStorage, ref, uploadBytes, uploadString, connectStorageEmulator } from "firebase/storage"; import { file } from "./my-module.js"; const firebaseConfig = { apiKey: "...", authDomain: "...", databaseURL: "...", projectId: "...", storageBucket: "...", messagingSenderId: "...", appId: "..." }; initializeApp(firebaseConfig); export const ByteMe = functions.firestore.document('ByteMe/{userID}').onUpdate((change, context) => { const storage = getStorage(app); // const storageRef = ref(storage, 'hello.txt'); // location to write to // connectStorageEmulator(storage, "localhost", 9199); // comment out to write to the cloud const storageRef = ref(storage, 'gs://my-awesome-app.appspot.com/Pictures/hello.txt'); const metadata = { contentType: 'text/plain', }; async function uploadFile() { try { console.log(file); await uploadBytes(storageRef, file, metadata); console.log('Uploaded a file!'); } catch (error) { console.error(error); } } return uploadFile(); });
日志输出Hello world(确认文件可被函数读取),但还是抛出相同的byteLength错误,为何uploadBytes无法读取该文件?
切换到模拟器也会触发相同错误。
4. 从API获取文件上传仍触发相同错误
尝试从API获取音频文件后上传,代码如下:
import { initializeApp } from "firebase/app"; import * as functions from "firebase-functions"; import { getStorage, ref, uploadBytes, uploadString, connectStorageEmulator } from "firebase/storage"; import got from 'got'; const firebaseConfig = { apiKey: "...", authDomain: "...", databaseURL: "...", projectId: "...", storageBucket: "...", messagingSenderId: "...", appId: "..." }; initializeApp(firebaseConfig); export const ByteAPI = functions.firestore.document('ByteAPI/{userID}').onUpdate((change, context) => { const storage = getStorage(); // const storageRef = ref(storage, 'picture.jpg'); // location to write to // connectStorageEmulator(storage, "localhost", 9199); // comment out to write to the cloud const storageRef = ref(storage, 'gs://my-awesome-app.appspot.com/Pictures/winter.mp3'); const metadata = { contentType: 'audio/mpeg', }; async function uploadFile() { try { let file = await got('https://audio.oxforddictionaries.com/en/mp3/winter__us_2.mp3'); await uploadBytes(storageRef, file, metadata); console.log('Uploaded a file!'); } catch (error) { console.error(error); } } return uploadFile(); });
目标音频文件可正常访问,但无论云端还是模拟器,都抛出相同的byteLength错误。尝试上传Uint8Array结果一样,难道uploadBytes功能损坏了?
解决方案
核心问题:uploadBytes参数类型不匹配
uploadBytes要求第二个参数必须是Blob、File或Uint8Array类型,你传入的普通字符串、got响应对象都不符合要求,这才导致了byteLength读取错误。
分场景修复:
- 上传字符串(用
uploadBytes)
将字符串转换为Uint8Array后传入:
const fileBuffer = new TextEncoder().encode(file); await uploadBytes(storageRef, fileBuffer, metadata);
- 处理API获取的文件(got库)
got默认返回响应对象,需指定responseType: 'buffer'获取二进制数据,再传入响应体:
let response = await got('https://audio.oxforddictionaries.com/en/mp3/winter__us_2.mp3', { responseType: 'buffer' }); await uploadBytes(storageRef, response.body, metadata);
- 修复Storage模拟器不工作问题
- 确保模拟器正确启动:运行
firebase emulators:start --only storage - 调整调用顺序:必须先连接模拟器,再创建存储引用
const storage = getStorage(); connectStorageEmulator(storage, "localhost", 9199); // 先连接模拟器 const storageRef = ref(storage, 'message.txt'); // 再创建引用 - 检查端口是否冲突,默认Storage模拟器端口为9199
- 调整函数超时时间:在
firebase.json中配置{ "functions": { "runtimeOptions": { "timeoutSeconds": 30 } } }
- 解决函数超时问题
服务端场景更推荐使用Firebase Admin SDK,而非Web版Storage SDK,兼容性更好:
安装Admin SDK:
npm install firebase-admin
重构代码示例:
import * as functions from "firebase-functions"; import { initializeApp } from "firebase-admin/app"; import { getStorage } from "firebase-admin/storage"; initializeApp(); export const StringMe = functions.firestore.document('StringMe/{userID}').onUpdate(async (change, context) => { const bucket = getStorage().bucket(); const file = bucket.file('Pictures/message.txt'); await file.save("Hello world!", { contentType: 'text/plain' }); console.log("Uploaded a string!"); });
内容的提问来源于stack exchange,提问作者Thomas David Kehoe

