处理函数中的错误
本页介绍了如何处理 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 集合;用于跟踪主函数的失败执行。
处理程序函数;用于调用主函数,并在函数失败时将其记录到失败执行跟踪器集合中。
数据库触发器函数;每当处理程序函数将错误添加到失败执行跟踪器集合时,就会重新运行处理程序函数。
您可以使用一组处理程序函数、执行跟踪器集合和数据库触发器函数来支持多个主函数。
优点 | 缺点 |
---|---|
|
|
创建函数以处理执行重试
首先,创建调用主函数的处理程序函数 handleRetry
。
handleRetry
接受以下参数:
Parameter | 类型 | 说明 |
---|---|---|
functionToRetry | JavaScript 函数 | 要重试的函数。 |
functionName | 字符串 | 您要重试的函数名称。 |
operationId | ObjectId | 主函数执行的唯一标识符,包括重试。 |
previousRetries | 数值 | 以前尝试执行主函数的次数。 |
...args | 其余参数 | 传递给主函数的不确定数量的参数。 |
handleRetry
执行以下操作:
尝试在
try
语句中执行functionToRetry
。如果执行成功,则handleRetry
将返回由以下内容返回的值functionToRetry
。如果在上一步中执行
functionToRetry
而引发错误,则catch
语句按如下方式处理该错误:检查以前的重试次数是否等于允许的最大重试次数。如果两个数字相同,则函数会发生错误,因为已达到最大重试次数。该函数不再尝试重试。
创建一个函数执行日志条目对象以插入到数据库中。
获取对失败执行跟踪器集合的引用。
将函数日志执行日志条目插入失败的执行跟踪器集合中。此插入操作会导致数据库触发器函数(您将在下一步中触发该函数)。
主函数是作为 functionToRetry
参数传递的。handleRetry
尝试执行主函数。如果执行失败,该函数尝试重试主函数。
导航至 Functions 。 单击按钮Create New Function 。
在字段 Name 中,添加 handleRetry
。
在 Function Editor 中添加以下代码,然后保存该函数:
// 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;
将以下内容添加到functions/config.json
中:
[ { "name": "handleRetry", "private": true, "run_as_system": true } // ...other configuration ]
为函数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
创建重试数据库触发器
在应用的用户界面中前往 Triggers。
单击 Add a Trigger 按钮。
使用以下配置创建触发器:
字段值名称您选择的名称(例如:retryOperation
)已启用是Skip Events on Re-Enable是事件排序是集群名称您选择的名称(例如:mongodb-atlas
)Database Name您选择的名称(例如:logs
)集合名称您选择的名称(例如:failed_execution_logs
)操作类型Insert完整文档是文档原像NoSelect an Event Type(选择事件类型)functionfunction单击 + New Function。有关函数内容,请参阅以下信息。高级配置无需高级配置。
为数据库trigger添加配置。 有关详细信息,请参阅trigger配置参考。
{ "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,请添加以下代码,然后保存触发器:
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
:
[ // ...other configuration { "name": "retryOperationDbTrigger", "private": true } ]
将以下代码添加到文件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;
将更改推送到App Services:
appservices push
编写要重试的函数
现在您有了函数处理程序和重试数据库触发器函数,您可以编写主函数。
在以下示例中,函数在执行加法时随机出现错误。执行此逻辑的 JavaScript 函数如下:
getRandomOneTwoThree()
:用于为示例生成错误的辅助函数。additionOrFailure()
:使用主逻辑的函数。
调用重试处理程序包装的 additionOrFailure()
是在导出的函数 additionWithRetryHandler()
中进行的。所有使用重试处理函数的函数应类似于该函数。
必须包含正确的参数,才能使此函数与其余重试逻辑一起工作。这些参数包括:
Parameter | 类型 | 说明 |
---|---|---|
...args | 其余参数 | 使用主逻辑传递给函数的零个或多个参数。在本例中, additionOrFailure() 对 num1 和 num2 这两个数字进行相加。 |
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,添加以下代码并保存函数:
// 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
:
[ // ...other configuration { "name": "additionWithRetryHandler", "private": false } ]
将以下代码添加到文件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
时,如果失败,函数将重试。