Skip to main content

Invoking Other Cloud Functions within Cloud Functions

In cloud function development, you may need to invoke one cloud function from another to achieve feature modularization, code reuse, and business logic decoupling. CloudBase supports invoking cloud functions within the current environment or other environments.

Application Scenarios

Typical Use Cases

  • Feature Modularization: Splitting complex business into multiple independent cloud functions
  • Code Reuse: Sharing common business logic and utility functions
  • Permission Isolation: Operations with different permission levels are processed separately.
  • Asynchronous Processing: Triggering background tasks and asynchronous operations
  • Cross-Environment Invocation: Function invocation between development, testing, and production environments

Practical Application Example

// The order processing function invokes multiple sub-functions
exports.main = async (event, context) => {
// 1. Verify user permissions
const authResult = await app.callFunction({
name: 'user-auth',
data: { userId: event.userId, action: 'create_order' }
});

// 2. Calculate the order amount
const priceResult = await app.callFunction({
name: 'price-calculator',
data: { items: event.items, couponCode: event.couponCode }
});

// 3. Send notification
await app.callFunction({
name: 'notification-sender',
data: { type: 'order_created', orderId: orderId }
});

return { success: true, orderId: orderId };
};

Overview of Invocation Methods

Using the Node.js SDK for Invocation

The Node.js SDK is the recommended approach for invoking other cloud functions within a cloud function, offering a concise API and robust error handling.

Basic Invocation

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
// Initialize the SDK (automatically obtains current environment information)
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV // Use the current environment
});

try {
// Invoke a cloud function within the same environment
const result = await app.callFunction({
name: 'target-function',
data: {
message: 'Hello from caller function',
timestamp: Date.now(),
userId: event.userId
}
});

console.log('Function invoked successfully:', result);
return {
success: true,
data: result.result,
requestId: result.requestId
};
} catch (error) {
console.error('Function invocation failed:', error);
return {
success: false,
error: error.message,
code: error.code
};
}
};

Advanced Features

Version Control Invocation

exports.main = async (event, context) => {
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV
});

// Invoke the specified version of the cloud function
const result = await app.callFunction({
name: 'versioned-function',
data: event.data,
qualifier: '2' // Invoke version 2
});

return result.result;
};

Timeout Control

exports.main = async (event, context) => {
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV
});

try {
// Set a 5-second timeout
const result = await app.callFunction({
name: 'slow-function',
data: event.data
}, {
timeout: 5000 // 5 seconds timeout
});

return result.result;
} catch (error) {
if (error.code === 'TIMEOUT') {
return { error: 'Function invocation timeout' };
}
throw error;
}
};

Batch Invocation

exports.main = async (event, context) => {
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV
});

// Concurrently invoke multiple functions
const promises = event.tasks.map(task =>
app.callFunction({
name: 'worker-function',
data: task
}).catch(error => ({ error: error.message }))
);

const results = await Promise.allSettled(promises);

return {
total: results.length,
success: results.filter(r => r.status === 'fulfilled').length,
failed: results.filter(r => r.status === 'rejected').length,
results: results.map(r =>
r.status === 'fulfilled' ? r.value.result : r.reason
)
};
};

Functional Cloud Hosting Invocation

exports.main = async (event, context) => {
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV
});

// Invoke functional cloud hosting service
const result = await app.callFunction({
name: 'cloudrun-service',
type: 'cloudrun', // Specify as cloud hosting type
method: 'POST', // HTTP method
path: '/api/users', // Request path
header: { // request headers
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
},
data: { // Request body
name: event.userName,
email: event.userEmail
}
}, {
timeout: 10000 // 10 seconds timeout
});

return result.result;
};

Using HTTP API Invocation

Invoking cloud functions via the HTTP API is suitable for scenarios requiring finer-grained control or integration with external systems.

Basic HTTP Invocation

const https = require('https');

exports.main = async (event, context) => {
const accessToken = await getAccessToken(); // Obtain an access token

const postData = JSON.stringify({
message: 'Hello from HTTP caller',
data: event.data
});

const options = {
hostname: `${process.env.TCB_ENV}.api.tcloudbasegateway.com`,
port: 443,
path: '/v1/functions/target-function',
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData)
}
};

return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';

res.on('data', (chunk) => {
data += chunk;
});

res.on('end', () => {
try {
const result = JSON.parse(data);
resolve(result);
} catch (error) {
reject(error);
}
});
});

req.on('error', (error) => {
reject(error);
});

req.write(postData);
req.end();
});
};

// Helper function to obtain an access token
async function getAccessToken() {
// Implement the logic to obtain access_token
// Can be obtained from environment variables, databases, or other secure storage
return process.env.ACCESS_TOKEN;
}

Using Axios to Simplify HTTP Calls

const axios = require('axios');

exports.main = async (event, context) => {
try {
const response = await axios.post(
`https://${process.env.TCB_ENV}.api.tcloudbasegateway.com/v1/functions/target-function`,
{
message: 'Hello from Axios caller',
data: event.data
},
{
headers: {
'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
'Content-Type': 'application/json'
},
timeout: 10000 // 10 seconds timeout
}
);

return {
success: true,
data: response.data,
status: response.status
};
} catch (error) {
console.error('HTTP invocation failed:', error.response?.data || error.message);

return {
success: false,
error: error.response?.data?.message || error.message,
status: error.response?.status
};
}
};

Cross-Environment Invocation

Cross-environment invocation allows you to call cloud functions across different CloudBase environments, commonly used for data synchronization and business collaboration between development, testing, and production environments.

Configuring Cross-Environment Invocation

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
// Select the target environment based on different scenarios
const targetEnv = getTargetEnv(event.scenario);

const app = tcb.init({
env: targetEnv,
secretId: process.env.CROSS_ENV_SECRET_ID,
secretKey: process.env.CROSS_ENV_SECRET_KEY
});

const result = await app.callFunction({
name: 'sync-data',
data: {
sourceEnv: process.env.TCB_ENV,
syncType: event.syncType,
data: event.data
}
});

return result.result;
};

function getTargetEnv(scenario) {
const envMapping = {
'dev-to-test': 'test-env-id',
'test-to-prod': 'prod-env-id',
'prod-to-backup': 'backup-env-id'
};

return envMapping[scenario] || 'default-env-id';
}

Environment Variable Management

Set environment variables in the cloud function configuration:

# Adding Environment Variables in the Cloud Function Configuration of the TCB Console
CROSS_ENV_SECRET_ID=your-secret-id
CROSS_ENV_SECRET_KEY=your-secret-key
TARGET_ENV_ID=target-environment-id
ACCESS_TOKEN=your-access-token

Error Handling and Debugging

Comprehensive Error Handling

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV
});

try {
const result = await app.callFunction({
name: 'target-function',
data: event.data
});

return {
success: true,
data: result.result,
requestId: result.requestId
};
} catch (error) {
console.error('Function call failed:', {
code: error.code,
message: error.message,
requestId: error.requestId,
stack: error.stack
});

// Handle differently based on error types
switch (error.code) {
case 'FUNCTION_NOT_FOUND':
return {
success: false,
error: 'Target function does not exist',
code: 'FUNCTION_NOT_FOUND'
};
case 'PERMISSION_DENIED':
return {
success: false,
error: 'Insufficient permissions',
code: 'PERMISSION_DENIED'
};
case 'TIMEOUT':
return {
success: false,
error: 'Function invocation timeout',
code: 'TIMEOUT'
};
default:
return {
success: false,
error: error.message || 'Unknown error',
code: error.code || 'UNKNOWN_ERROR'
};
}
}
};

Debugging Techniques

exports.main = async (event, context) => {
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV
});

// Add debugging information
console.log('Invocation start:', {
caller: context.function_name,
target: 'target-function',
requestId: context.request_id,
timestamp: new Date().toISOString()
});

const startTime = Date.now();

try {
const result = await app.callFunction({
name: 'target-function',
data: {
...event.data,
_debug: {
caller: context.function_name,
callTime: new Date().toISOString()
}
}
});

const duration = Date.now() - startTime;
console.log('Invoked successfully:', {
duration: `${duration}ms`,
resultSize: JSON.stringify(result.result).length,
requestId: result.requestId
});

return result.result;
} catch (error) {
const duration = Date.now() - startTime;
console.error('Invocation failure:', {
duration: `${duration}ms`,
error: error.message,
code: error.code
});

throw error;
}
};

Performance Optimization Recommendations

1. SDK Instance Reuse

// ❌ Error: Initialize on every call
exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
return await app.callFunction({ name: 'target', data: event });
};

// ✅ Correct approach: Reuse SDK instances
const tcb = require('@cloudbase/node-sdk');
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });

exports.main = async (event, context) => {
return await app.callFunction({ name: 'target', data: event });
};

2. Concurrency Control

// Limit the number of concurrent calls
class ConcurrencyController {
constructor(maxConcurrent = 5) {
this.maxConcurrent = maxConcurrent;
this.running = 0;
this.queue = [];
}

async execute(fn) {
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject });
this.process();
});
}

async process() {
if (this.running >= this.maxConcurrent || this.queue.length === 0) {
return;
}

this.running++;
const { fn, resolve, reject } = this.queue.shift();

try {
const result = await fn();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.process();
}
}
}

const controller = new ConcurrencyController(3);

exports.main = async (event, context) => {
const tasks = event.tasks.map(task =>
() => app.callFunction({ name: 'worker', data: task })
);

const results = await Promise.all(
tasks.map(task => controller.execute(task))
);

return results;
};

3. Cache Policy

// Simple in-memory cache
const cache = new Map();

async function cachedCallFunction(functionName, data, ttl = 60000) {
const cacheKey = `${functionName}_${JSON.stringify(data)}`;
const cached = cache.get(cacheKey);

if (cached && Date.now() - cached.timestamp < ttl) {
console.log('Cache hit:', cacheKey);
return cached.result;
}

const result = await app.callFunction({
name: functionName,
data: data
});

cache.set(cacheKey, {
result: result.result,
timestamp: Date.now()
});

return result.result;
}

Best Practices

1. Function Design Principles

// Design reusable utility functions
exports.main = async (event, context) => {
const { action, data } = event;

switch (action) {
case 'validate_user':
return await validateUser(data);
case 'send_notification':
return await sendNotification(data);
case 'calculate_price':
return await calculatePrice(data);
default:
throw new Error(`Unsupported operation: ${action}`);
}
};

async function validateUser(userData) {
// User authentication logic
return { valid: true, userId: userData.id };
}

async function sendNotification(notificationData) {
// Notification sending logic
return { sent: true, messageId: 'msg_123' };
}

async function calculatePrice(priceData) {
// Price calculation logic
return { total: 100, discount: 10 };
}

2. Error Retry Mechanism

async function callFunctionWithRetry(functionName, data, maxRetries = 3) {
let lastError;

for (let i = 0; i < maxRetries; i++) {
try {
const result = await app.callFunction({
name: functionName,
data: data
});
return result.result;
} catch (error) {
lastError = error;

// Retry only for specific errors
if (error.code === 'TIMEOUT' || error.code === 'NETWORK_ERROR') {
console.log(`Retry ${i + 1}/${maxRetries}:`, error.message);
await sleep(1000 * (i + 1)); // Exponential backoff
continue;
}

// Other errors are thrown directly.
throw error;
}
}

throw lastError;
}

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

3. Monitoring and Logging

exports.main = async (event, context) => {
const startTime = Date.now();
const callId = `${context.request_id}_${Date.now()}`;

console.log('Function invocation start:', {
callId,
caller: context.function_name,
target: event.targetFunction,
timestamp: new Date().toISOString()
});

try {
const result = await app.callFunction({
name: event.targetFunction,
data: {
...event.data,
_meta: {
callId,
caller: context.function_name,
timestamp: new Date().toISOString()
}
}
});

const duration = Date.now() - startTime;
// Function invocation successful:
callId,
duration: `${duration}ms`,
resultSize: JSON.stringify(result.result).length
});

return result.result;
} catch (error) {
const duration = Date.now() - startTime;
console.error('Function call failed:', {
callId,
duration: `${duration}ms`,
error: error.message,
code: error.code
});

throw error;
}
};