Docs 菜单
Docs 主页
/ /
Atlas App Services
/

处理函数中的错误

在此页面上

  • 基本错误处理
  • 查看日志
  • 重试函数
  • 在错误处理块中递归调用函数
  • 使用数据库触发器重试

本页介绍了如何处理 Atlas Function 中的错误。

注意

使用 AWS EventBridge 进行数据库触发器的自定义错误处理

您可以使用 AWS EventBridge 创建专门为数据库触发器创建的自定义错误处理程序。 有关详细信息,请参阅自定义错误处理。

您可以使用标准JavaScript错误处理技术(例如 try...catch 语句 )来处理函数错误。

function willThrowAndHandleError() {
try {
throw new Error("This will always happen");
} catch (err) {
console.error("An error occurred. Error message:" + err.message);
}
}
exports = willThrowAndHandleError;

您可以在 App Services 日志中查看所有函数执行的记录,包括错误导致无法成功执行的记录。

根据调用函数的方式,它在日志中的显示方式会有所不同。例如,Atlas Triggers 调用的函数在日志中显示为“Triggers”,而从 Realm 客户端 SDK 中调用的函数在日志中显示为“Functions”。有关更多信息,请参阅日志条目类型文档

Atlas Functions 没有内置的重试行为。您可以添加自定义重试行为。例如,如果函数调用的第三方服务具有间歇性连接,并且您希望即使第三方服务器暂时关闭,函数也能重新执行,则您可能希望添加重试行为。

本节介绍了在函数中添加重试行为的以下策略:

您可以递归调用“函数”来处理可能失败的操作。

在高级别上,这一过程包括以下组成部分:

  • try 语句中执行要重试的操作,并在catch 语句中调用函数本身。

  • 要防止无限期执行,请设置最大重试次数。每次函数失败并进入 catch 语句时,增加当前重试次数的计数。在函数的当前重试次数达到最大重试次数时,停止递归执行。

  • 您可能还希望限制重试次数以减少某个时间范围内的执行总数。

下表描述了使用递归调用策略处理函数重试的一些优点和缺点。

优点
缺点
  • 所有重试逻辑都发生在一个函数内。

  • 函数可以在重试后返回一个值。

  • 最少的额外代码。

  • 所有重试必须在单个函数的最大执行时间内进行。

以下代码示例演示了通过在错误处理区块中使用递归来重试函数的实现。

// Utility function to suspend execution of current process
async function sleep(milliseconds) {
await new Promise((resolve) => setTimeout(resolve, milliseconds));
}
// Set variables to be used by all calls to `mightFail`
// Tip: You could also store `MAX_RETRIES` and `THROTTLE_TIME_MS`
// in App Services Values
const MAX_RETRIES = 5;
const THROTTLE_TIME_MS = 5000;
let currentRetries = 0;
let errorMessage = "";
async function mightFail(...inputVars) {
if (currentRetries === MAX_RETRIES) {
console.error(
`Reached maximum number of retries (${MAX_RETRIES}) without successful execution.`
);
console.error("Error Message:", errorMessage);
return;
}
let res;
try {
// operation that might fail
res = await callFlakyExternalService(...inputVars);
} catch (err) {
errorMessage = err.message;
// throttle retries
await sleep(THROTTLE_TIME_MS);
currentRetries++;
res = await mightFail(...inputVars);
}
return res;
}
exports = mightFail;

您还可以使用数据库触发器执行重试,并使用 MongoDB 集合跟踪以前失败的执行以重试函数。

在高级别上,这一过程包括以下组成部分:

  • 主函数执行您想要重试的逻辑,包装在处理函数中(请参见下面的要点)。

  • 失败执行跟踪器 MongoDB 集合;用于跟踪主函数的失败执行。

  • 处理程序函数;用于调用主函数,并在函数失败时将其记录到失败执行跟踪器集合中。

  • 数据库触发器函数;每当处理程序函数将错误添加到失败执行跟踪器集合时,就会重新运行处理程序函数。

您可以使用一组处理程序函数、执行跟踪器集合和数据库触发器函数来支持多个主函数。

优点
缺点
  • 每次重试都是其自己的函数执行,具有自己的最大执行时间和资源。

  • 如果重试函数,则无法返回值。

  • 每个函数调用都需要调用函数两次,一次用于函数本身,一次用于重试处理程序。

  • 更复杂的逻辑,可能更难编写、调试和监控。

1

首先,创建调用主函数的处理程序函数 handleRetry

handleRetry 接受以下参数:

Parameter
类型
说明
functionToRetry
JavaScript 函数
要重试的函数。
functionName
字符串
您要重试的函数名称。
operationId
ObjectId
主函数执行的唯一标识符,包括重试。
previousRetries
数值
以前尝试执行主函数的次数。
...args
其余参数
传递给主函数的不确定数量的参数。

handleRetry 执行以下操作:

  1. 尝试在 try 语句中执行 functionToRetry。如果执行成功,则 handleRetry 将返回由以下内容返回的值 functionToRetry

  2. 如果在上一步中执行 functionToRetry 而引发错误,则catch 语句按如下方式处理该错误:

    1. 检查以前的重试次数是否等于允许的最大重试次数。如果两个数字相同,则函数会发生错误,因为已达到最大重试次数。该函数不再尝试重试。

    2. 创建一个函数执行日志条目对象以插入到数据库中。

    3. 获取对失败执行跟踪器集合的引用。

    4. 将函数日志执行日志条目插入失败的执行跟踪器集合中。此插入操作会导致数据库触发器函数(您将在下一步中触发该函数)。

主函数是作为 functionToRetry 参数传递的。handleRetry 尝试执行主函数。如果执行失败,该函数尝试重试主函数。

导航至 Functions 。 单击按钮Create New Function

在字段 Name 中,添加 handleRetry

Function Editor 中添加以下代码,然后保存该函数:

handleRetry.js
// Tip: You could also put this in an App Services Value
const MAX_FUNC_RETRIES = 5;
async function handleRetry(
functionToRetry,
functionName,
operationId,
previousRetries,
...args
) {
try {
// Try to execute the main function
const response = await functionToRetry(...args);
return response;
} catch (err) {
// Evaluates if should retry function again.
// If no retry, throws error and stops retrying.
if (previousRetries === MAX_FUNC_RETRIES) {
throw new Error(
`Maximum number of attempts reached (${MAX_FUNC_RETRIES}) for function '${functionName}': ${err.message}`
);
}
// Build function execution log entry for insertion into database.
const logEntry = {
operationId,
errorMessage: err.message,
timestamp: new Date(),
retries: previousRetries + 1,
args,
functionName,
};
// Get reference to database collection
const executionLog = context.services
.get("mongodb-atlas")
.db("logs")
.collection("failed_execution_logs");
// Add execution log entry to database
await executionLog.insertOne(logEntry);
return;
}
}
exports = handleRetry;

提示

安装和设置App Services CLI

如果使用CLI更新App Services App,则必须首先安装并设立App Services CLI。

将以下内容添加到functions/config.json中:

functions/config.json
[
{
"name": "handleRetry",
"private": true,
"run_as_system": true
}
// ...other configuration
]

为函数functions/handleRetry.js创建文件:

functions/handleRetry.js
// Tip: You could also put this in an App Services Value
const MAX_FUNC_RETRIES = 5;
async function handleRetry(
functionToRetry,
functionName,
operationId,
previousRetries,
...args
) {
try {
// Try to execute the main function
const response = await functionToRetry(...args);
return response;
} catch (err) {
// Evaluates if should retry function again.
// If no retry, throws error and stops retrying.
if (previousRetries === MAX_FUNC_RETRIES) {
throw new Error(
`Maximum number of attempts reached (${MAX_FUNC_RETRIES}) for function '${functionName}': ${err.message}`
);
}
// Build function execution log entry for insertion into database.
const logEntry = {
operationId,
errorMessage: err.message,
timestamp: new Date(),
retries: previousRetries + 1,
args,
functionName,
};
// Get reference to database collection
const executionLog = context.services
.get("mongodb-atlas")
.db("logs")
.collection("failed_execution_logs");
// Add execution log entry to database
await executionLog.insertOne(logEntry);
return;
}
}
exports = handleRetry;

将更改推送到App Services:

appservices push
2
  1. 在应用的用户界面中前往 Triggers

  2. 单击 Add a Trigger 按钮。

  3. 使用以下配置创建触发器:

    字段
    名称
    您选择的名称(例如:retryOperation
    已启用
    Skip Events on Re-Enable
    事件排序
    集群名称
    您选择的名称(例如:mongodb-atlas
    Database Name
    您选择的名称(例如:logs
    集合名称
    您选择的名称(例如:failed_execution_logs
    操作类型
    Insert
    完整文档
    文档原像
    No
    Select an Event Type(选择事件类型)
    function
    function
    单击 + New Function。有关函数内容,请参阅以下信息。
    高级配置
    无需高级配置。

为数据库trigger添加配置。 有关详细信息,请参阅trigger配置参考。

triggers/retryOperation.json
{
"name": "retry",
"type": "DATABASE",
"config": {
"operation_types": ["INSERT"],
"database": "logs",
"collection": "failed_execution_logs",
"service_name": "mongodb-atlas",
"project": {},
"full_document": true,
"full_document_before_change": false,
"unordered": false,
"skip_catchup_events": false
},
"disabled": false,
"event_processors": {
"FUNCTION": {
"config": {
"function_name": "retryOperationDbTrigger"
}
}
}
}

现在,为触发器调用的函数添加代码。

retryOperation 函数将重试处理程序发布到失败执行跟踪器集合的文档作为 logEntry 参数。然后,retryOperation 使用来自 logEntry 的信息通过 context.functions.execute() 调用主函数。

在字段 Function Name 中,添加 retryOperationDbTrigger

对于字段 Function,请添加以下代码,然后保存触发器:

functions/retryOperationDbTrigger.js
async function retryOperation({ fullDocument: logEntry }) {
// parse values from log entry posted to database
const { args, retries, functionName, operationId } = logEntry;
// Re-execute the main function
await context.functions.execute(functionName, ...args, operationId, retries);
}
exports = retryOperation;

将函数元数据添加到functions/config.json

functions/config.json
[
// ...other configuration
{
"name": "retryOperationDbTrigger",
"private": true
}
]

将以下代码添加到文件functions/retryOperationDbTrigger.js中:

retryOperationDbTrigger.js
async function retryOperation({ fullDocument: logEntry }) {
// parse values from log entry posted to database
const { args, retries, functionName, operationId } = logEntry;
// Re-execute the main function
await context.functions.execute(functionName, ...args, operationId, retries);
}
exports = retryOperation;

将更改推送到App Services:

appservices push
3

现在您有了函数处理程序和重试数据库触发器函数,您可以编写主函数。

在以下示例中,函数在执行加法时随机出现错误。执行此逻辑的 JavaScript 函数如下:

  • getRandomOneTwoThree():用于为示例生成错误的辅助函数。

  • additionOrFailure():使用主逻辑的函数。

调用重试处理程序包装的 additionOrFailure() 是在导出的函数 additionWithRetryHandler() 中进行的。所有使用重试处理函数的函数应类似于该函数。

必须包含正确的参数,才能使此函数与其余重试逻辑一起工作。这些参数包括:

Parameter
类型
说明
...args
其余参数
使用主逻辑传递给函数的零个或多个参数。在本例中,additionOrFailure()num1num2 这两个数字进行相加。
operationId
函数调用和重试的唯一标识符。将默认值设置为 new BSON.ObjectId()
retries
数值
将默认值设置为 0。

additionWithRetryHandler 正文是 context.functions.execute() 调用的重试处理程序 handleRetry,而该重试处理程序又会调用 additionOrFailure。您传递给 context.functions.execute() 的参数如下所示:

Argument
类型
说明
"handleRetry"
字符串
您定义的函数名称,用于调用主函数,并在主函数未正确执行时发布到重试日志。
additionOrFailure
JavaScript 函数
handleRetry() 调用的主函数。
operationId
BSON.ObjectId
作为参数从 additionWithRetryHandler()operationId 参数中传入。
retries
数值
作为参数从 additionWithRetryHandler()retries 参数中传入。
...args
展开参数
使用主逻辑传递给函数的零个或更多参数。作为参数从 additionWithRetryHandler()...args 参数中传入

在字段 Function Name 中,添加 additionWithRetryHandler

对于字段 Function,添加以下代码并保存函数:

additionWithRetryHandler.js
// randomly generates 1, 2, or 3
function getRandomOneTwoThree() {
return Math.floor(Math.random() * 3) + 1;
}
function additionOrFailure(num1, num2) {
// Throw error if getRandomOneTwoThree returns 1
const rand = getRandomOneTwoThree();
if (rand === 1) throw new Error("Uh oh!!");
const sum = num1 + num2;
console.log(`Successful addition of ${num1} + ${num2}. Result: ${sum}`);
// Otherwise return the sum
return sum;
}
async function additionWithRetryHandler(
inputVar1,
inputVar2,
// create a new `operation_id` if one not provided
operationId = new BSON.ObjectId(),
// count number of attempts
retries = 0
) {
const res = await context.functions.execute(
"handleRetry",
additionOrFailure,
"additionWithRetryHandler", // MUST BE NAME OF FUNCTION
operationId,
retries,
inputVar1,
inputVar2
);
return res;
}
exports = additionWithRetryHandler;

将函数元数据添加到functions/config.json

functions/config.json
[
// ...other configuration
{
"name": "additionWithRetryHandler",
"private": false
}
]

将以下代码添加到文件functions/additionWithRetryHandler.js中:

functions/additionWithRetryHandler.js
// randomly generates 1, 2, or 3
function getRandomOneTwoThree() {
return Math.floor(Math.random() * 3) + 1;
}
function additionOrFailure(num1, num2) {
// Throw error if getRandomOneTwoThree returns 1
const rand = getRandomOneTwoThree();
if (rand === 1) throw new Error("Uh oh!!");
const sum = num1 + num2;
console.log(`Successful addition of ${num1} + ${num2}. Result: ${sum}`);
// Otherwise return the sum
return sum;
}
async function additionWithRetryHandler(
inputVar1,
inputVar2,
// create a new `operation_id` if one not provided
operationId = new BSON.ObjectId(),
// count number of attempts
retries = 0
) {
const res = await context.functions.execute(
"handleRetry",
additionOrFailure,
"additionWithRetryHandler", // MUST BE NAME OF FUNCTION
operationId,
retries,
inputVar1,
inputVar2
);
return res;
}
exports = additionWithRetryHandler;

将更改推送到App Services:

appservices push

现在,在您调用 additionWithRetryHandler 时,如果失败,函数将重试。

后退

测试函数