如何编写云函数
函数入参详解
每个云函数调用都会收到两个重要对象:event
和 context
。
event 对象
event
对象包含触发云函数的事件数据,其内容根据触发方式不同而变化:
- 小程序调用:包含小程序端传入的参数
- HTTP 请求调用:包含 HTTP 请求信息(如请求头、请求体等)
- 定时触发:包含定时触发的相关信息
context 对象
context
对象提供调用上下文信息,帮助您了解函数的运行环境和调用方式:
- 请求 ID:当前调用的唯一标识符
- 调用来源:触发函数的服务或客户端信息
- 执行环境:函数的运行时信息
- 用户身份:调用方的身份信息(如有)
基础代码示例
以下是一个简单的 Node.js 云函数示例,展示如何处理入参并返回结果:
// index.js - 云函数入口文件
exports.main = async (event, context) => {
// 1. 解析云函数入参
const { a, b } = event;
// 2. 执行业务逻辑
const sum = a + b;
// 3. 返回结果
return {
sum,
timestamp: Date.now(),
requestId: context.requestId
};
};
异步处理最佳实践
由于实例的管理由平台自动处理,推荐云函数采用 async/await 模式,避免使用 Promise 链式调用:
exports.main = async (event, context) => {
// ❌ 不推荐:Promise 链式调用
getList().then((res) => {
// do something...
});
// ✅ 推荐:使用 async/await
const res = await getList();
// do something...
};
环境变量使用
云函数可以通过 process.env
获取环境变量,这是管理配置信息的最佳实践:
获取环境变量
exports.main = async (event, context) => {
// 获取环境变量
const dbUrl = process.env.DATABASE_URL;
const apiKey = process.env.API_KEY;
const nodeEnv = process.env.NODE_ENV || 'development';
// 使用环境变量进行配置
const config = {
database: dbUrl,
apiKey: apiKey,
debug: nodeEnv === 'development'
};
return {
message: '环境变量获取成功',
environment: nodeEnv
};
};
环境变量最佳实践
exports.main = async (event, context) => {
// 检查必需的环境变量
const requiredEnvVars = ['DATABASE_URL', 'API_KEY'];
const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);
if (missingVars.length > 0) {
throw new Error(`缺少必需的环境变量: ${missingVars.join(', ')}`);
}
// 安全地使用环境变量
const config = {
dbUrl: process.env.DATABASE_URL,
apiKey: process.env.API_KEY,
timeout: parseInt(process.env.TIMEOUT) || 5000
};
return { success: true, config };
};
- 敏感信息(如 API 密钥、数据库连接字符串)应通过环境变量传递,不要硬编码在代码中
- 环境变量值始终是字符串类型,需要时请进行类型转换
- 建议为环境变量设置默认值,提高代码的健壮性
时区设置
云函数的运行环境内保持的是 UTC 时间,即 0 时区时间,和北京时间有 8 小时的时间差。
可以通过语言的时间处理相关库或代码包(如 moment-timezone),识别 UTC 时间并转换为+8 区北京时间。
当前云开发支持的函数版本为 Node 10,无法通过设置环境变量 TZ=Asia/Shanghai 指定时区(Node 15+版本支持)。
时区处理示例
const moment = require("moment-timezone"); // 需在 package.json 中指定并安装依赖
exports.main = async (event, context) => {
// javascript date
console.log(new Date()); // 2021-03-16T08:04:07.441Z (UTC+0)
console.log(moment().tz("Asia/Shanghai").format()); // 2021-03-16T16:04:07+08:00 (UTC+8)
// 获取当前北京时间
const beijingTime = moment().tz("Asia/Shanghai");
return {
utcTime: new Date().toISOString(),
beijingTime: beijingTime.format(),
timestamp: beijingTime.valueOf()
};
};
时区处理最佳实践
exports.main = async (event, context) => {
const moment = require("moment-timezone");
// 统一时区处理函数
const getBeijingTime = (date = new Date()) => {
return moment(date).tz("Asia/Shanghai");
};
// 格式化时间输出
const formatTime = (date, format = 'YYYY-MM-DD HH:mm:ss') => {
return getBeijingTime(date).format(format);
};
// 业务逻辑中使用
const currentTime = getBeijingTime();
const formattedTime = formatTime();
console.log('当前北京时间:', formattedTime);
return {
success: true,
currentTime: formattedTime,
timestamp: currentTime.valueOf()
};
};
使用 ES Module 规范
在云函数 Node.js 环境中无法直接采用 ES Module 规范编写代码,主要原因在于,云函数默认支持的入口文件(index.js
)必须遵循 CommonJS 规范,并且文件名必须为 「index.js」。然而,Node.js 对于符合 ES Module 规范的模块文件要求其扩展名为 .mjs
。
全新云函数中使用 ES Module
1. 创建 Nodejs v14+ 环境云函数
Nodejs 版本必须大于等于14才支持 ES Module。
2. 创建入口文件 entry.mjs
创建一个名为 entry.mjs
的文件,作为 ES Module 入口点:
// entry.mjs
import { foo } from './util.js';
// 入口函数
export const entry = (event, context) => {
return foo(event, context);
};
3. 在 index.js 中引入入口文件
// index.js
exports.main = async (event, context) => {
const { entry } = await import('./entry.mjs');
return entry(event, context);
};
4. 创建业务模块
// util.js
export const foo = async (event, context) => {
console.log('使用 ES Module 语法');
return {
success: true,
message: 'ES Module 调用成功',
data: event
};
};
改造现有云函数支持 ES Module
对于现有的云函数代码想要适配 ES Module 规范,本着对代码最小侵入性以及改造成本最低原则:
1. 创建入口文件 entry.mjs
// entry.mjs
// 假设src文件夹是现有的业务代码
import { businessLogic } from './src/index.js';
// 入口函数
export const entry = (event, context) => {
return businessLogic(event, context);
};
2. 创建 package.json 文件
在业务代码目录下创建 package.json
文件:
{
"type": "module"
}
这将告诉 Node.js 将此目录视为 ES Module,并允许使用 ESM 语法导入和导出模块。
3. 修改现有业务代码
将现有的 CommonJS 语法改为 ES Module 语法:
// src/index.js - 改造前(CommonJS)
const helper = require('./helper');
module.exports = {
businessLogic: async (event, context) => {
return helper.process(event);
}
};
// src/index.js - 改造后(ES Module)
import { process } from './helper.js';
export const businessLogic = async (event, context) => {
return process(event);
};
ES Module 最佳实践
// entry.mjs
import { createRequire } from 'module';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
// 在 ES Module 中使用 require(如果需要)
const require = createRequire(import.meta.url);
// 获取当前文件路径(ES Module 中的 __dirname 替代)
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// 动态导入
const loadModule = async (moduleName) => {
try {
const module = await import(moduleName);
return module;
} catch (error) {
console.error(`模块加载失败: ${moduleName}`, error);
throw error;
}
};
export const entry = async (event, context) => {
// 条件导入
const moduleName = event.moduleType || 'default';
const handler = await loadModule(`./handlers/${moduleName}.js`);
return handler.default(event, context);
};
错误处理与日志记录
错误处理最佳实践
exports.main = async (event, context) => {
try {
// 参数验证
if (!event.userId) {
throw new Error('缺少必需参数: userId');
}
// 业务逻辑处理
const result = await processUserData(event.userId);
return {
success: true,
data: result
};
} catch (error) {
// 记录错误日志
console.error('函数执行失败:', {
error: error.message,
stack: error.stack,
event,
requestId: context.requestId
});
// 返回友好的错误信息
return {
success: false,
error: error.message,
requestId: context.requestId
};
}
};
日志记录规范
exports.main = async (event, context) => {
// 记录函数开始执行
console.log('函数开始执行:', {
requestId: context.requestId,
event: event
});
const startTime = Date.now();
try {
const result = await businessLogic(event);
// 记录成功日志
console.log('函数执行成功:', {
requestId: context.requestId,
duration: Date.now() - startTime,
resultSize: JSON.stringify(result).length
});
return result;
} catch (error) {
// 记录错误日志
console.error('函数执行失败:', {
requestId: context.requestId,
duration: Date.now() - startTime,
error: error.message
});
throw error;
}
};
性能优化建议
执行时间优化
exports.main = async (event, context) => {
const startTime = Date.now();
try {
// 使用并行处理提升性能
const promises = event.items.map(item => processItem(item));
const results = await Promise.all(promises);
const duration = Date.now() - startTime;
console.log(`函数执行耗时: ${duration}ms`);
return {
success: true,
data: results,
duration
};
} catch (error) {
console.error('执行错误:', error);
throw error;
}
};
内存使用优化
exports.main = async (event, context) => {
// 分批处理大数据,避免内存溢出
const batchSize = parseInt(process.env.BATCH_SIZE) || 100;
const results = [];
for (let i = 0; i < event.data.length; i += batchSize) {
const batch = event.data.slice(i, i + batchSize);
const batchResult = await processBatch(batch);
results.push(...batchResult);
// 及时清理不需要的变量
batch.length = 0;
// 记录处理进度
console.log(`已处理 ${Math.min(i + batchSize, event.data.length)}/${event.data.length} 条数据`);
}
return results;
};
连接池优化
// 在函数外部初始化连接池,实现连接复用
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
connectionLimit: 10,
acquireTimeout: 60000,
timeout: 60000
});
exports.main = async (event, context) => {
let connection;
try {
// 从连接池获取连接
connection = await pool.getConnection();
const [rows] = await connection.execute(
'SELECT * FROM users WHERE id = ?',
[event.userId]
);
return {
success: true,
data: rows
};
} catch (error) {
console.error('数据库操作失败:', error);
throw error;
} finally {
// 释放连接回连接池
if (connection) {
connection.release();
}
}
};
常见问题排查
函数超时问题
问题现象:函数执行时间超过配置的超时时间
排查步骤:
- 查看监控数据中的执行时间分布
- 检查日志中的具体执行步骤耗时
- 分析代码中的耗时操作
解决方案:
exports.main = async (event, context) => {
// 设置超时处理
const timeout = parseInt(process.env.FUNCTION_TIMEOUT) || 30000;
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('函数执行超时')), timeout - 1000);
});
try {
// 使用 Promise.race 实现超时控制
const result = await Promise.race([
businessLogic(event),
timeoutPromise
]);
return result;
} catch (error) {
if (error.message === '函数执行超时') {
console.error('函数执行超时,请优化代码逻辑');
}
throw error;
}
};
内存不足问题
问题现象:函数运行时内存使用超过配置限制
排查步骤:
- 查看监控数据中的内存使用情况
- 检查代码中的大对象创建和使用
- 分析是否存在内存泄漏
解决方案:
exports.main = async (event, context) => {
// 监控内存使用
const memoryUsage = process.memoryUsage();
console.log('初始内存使用:', memoryUsage);
try {
// 分批处理大数据
const results = [];
const batchSize = 50; // 根据内存限制调整批次大小
for (let i = 0; i < event.data.length; i += batchSize) {
const batch = event.data.slice(i, i + batchSize);
const batchResult = await processBatch(batch);
results.push(...batchResult);
// 强制垃圾回收(如果可用)
if (global.gc) {
global.gc();
}
// 检查内存使用
const currentMemory = process.memoryUsage();
console.log(`批次 ${Math.floor(i/batchSize) + 1} 内存使用:`, currentMemory);
}
return results;
} catch (error) {
console.error('内存使用异常:', process.memoryUsage());
throw error;
}
};
高错误率问题
问题现象:函数执行失败率较高
排查步骤:
- 查看日志中的错误信息
- 分析错误类型和出现频率
- 检查外部依赖服务状态
解决方案:
// 重试机制实现
async function retryOperation(operation, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
console.warn(`操作失败,第 ${i + 1} 次重试:`, error.message);
if (i === maxRetries - 1) {
throw error; // 最后一次重试失败,抛出错误
}
// 指数退避延迟
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
}
}
}
exports.main = async (event, context) => {
try {
// 参数校验
if (!event || typeof event !== 'object') {
throw new Error('无效的事件参数');
}
// 使用重试机制调用外部服务
const result = await retryOperation(async () => {
return await callExternalAPI(event.data);
});
return {
success: true,
data: result
};
} catch (error) {
// 详细的错误日志
console.error('函数执行失败:', {
error: error.message,
stack: error.stack,
event,
requestId: context.requestId,
timestamp: new Date().toISOString()
});
return {
success: false,
error: error.message,
requestId: context.requestId
};
}
};
开发调试技巧
本地调试
// 本地调试时的环境检测
const isLocal = process.env.NODE_ENV === 'development' || !process.env.TENCENTCLOUD_RUNENV;
exports.main = async (event, context) => {
// 本地调试时提供模拟数据
if (isLocal) {
console.log('本地调试模式');
event = event || { userId: 'test-user-123' };
context = context || { requestId: 'local-request-id' };
}
// 调试信息输出
if (process.env.DEBUG === 'true') {
console.log('调试信息:', {
event,
context,
env: process.env.NODE_ENV
});
}
return await businessLogic(event, context);
};
性能监控
exports.main = async (event, context) => {
const startTime = process.hrtime.bigint();
const startMemory = process.memoryUsage();
try {
const result = await businessLogic(event);
// 性能统计
const endTime = process.hrtime.bigint();
const endMemory = process.memoryUsage();
const duration = Number(endTime - startTime) / 1000000; // 转换为毫秒
console.log('性能统计:', {
duration: `${duration.toFixed(2)}ms`,
memoryDelta: {
rss: endMemory.rss - startMemory.rss,
heapUsed: endMemory.heapUsed - startMemory.heapUsed
}
});
return result;
} catch (error) {
console.error('执行异常:', error);
throw error;
}
};
通过合理使用测试、日志和监控功能,可以有效提升云函数的开发效率和运行稳定性。建议开发者在函数开发过程中充分利用这些工具,及时发现和解决问题。