处理函数中的错误
本页介绍了如何处理 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;
查看日志
您可以在 Logs 页面查看Atlas trigger调用的所有函数执行记录。 这包括由于错误而未能成功执行的函数。
有关更多信息,请参阅trigger日志。
重试函数
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 集合;用于跟踪主函数的失败执行。
处理程序函数;用于调用主函数,并在函数失败时将其记录到失败执行跟踪器集合中。
数据库触发器函数;每当处理程序函数将错误添加到失败执行跟踪器集合时,就会重新运行处理程序函数。
您可以使用一组处理程序函数、执行跟踪器集合和数据库触发器函数来支持多个主函数。
优点 | 缺点 |
---|---|
|
|
您可以在Atlas用户界面中或使用App Services CLI为函数创建重试机制。 以下过程将引导您创建处理程序、重试数据库trigger函数和主函数。
创建处理程序函数
首先,创建调用主函数的处理程序函数 handleRetry
。
handleRetry
接受以下参数:
Parameter | 类型 | 说明 |
---|---|---|
functionToRetry | JavaScript 函数 | 要重试的函数。 |
functionName | 字符串 | 您要重试的函数名称。 |
operationId | ObjectId | 主函数执行的唯一标识符,包括重试。 |
previousRetries | 数值 | 以前尝试执行主函数的次数。 |
...args | 其余参数 | 传递给主函数的不确定数量的参数。 |
handleRetry
执行以下操作:
尝试在
try
语句中执行functionToRetry
。如果执行成功,则handleRetry
将返回由以下内容返回的值functionToRetry
。如果在上一步中执行
functionToRetry
而引发错误,则catch
语句按如下方式处理该错误:检查以前的重试次数是否等于允许的最大重试次数。如果两个数字相同,则函数会发生错误,因为已达到最大重试次数。该函数不再尝试重试。
创建一个函数执行日志条目对象以插入到数据库中。
获取对失败执行跟踪器集合的引用。
将函数日志执行日志条目插入失败执行跟踪器集合。 此插入操作会导致您将在下一步中创建的数据库trigger函数触发。
主函数是作为 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;
将以下内容添加到
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; 部署更改:
运行以下命令以部署更改:
appservices push
创建重试数据库trigger
导航至Triggers页面:
如果尚未显示,请从导航栏上的 Organizations 菜单中选择包含项目的组织。
如果尚未显示,请从导航栏的 Projects 菜单中选择您的项目。
在侧边栏中,单击 Services 标题下的 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调用的函数中。
函数
retryOperation
将重试处理程序发布到失败执行跟踪器集合的文档作为参数logEntry
。 然后,retryOperation
使用context.functions.execute()使用来自logEntry
的信息调用主函数。在字段 Function Name 中,添加
retryOperationDbTrigger
。对于字段Function,添加以下代码,然后保存trigger :
functions/retryOperationDbTrigger.jsasync 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;
对MongoDB Atlas用户进行身份验证:
使用MongoDB Atlas Administration API密钥登录App Services CLI:
appservices login --api-key="<API KEY>" --private-api-key="<PRIVATE KEY>" 拉取应用的最新配置文件:
运行以下命令以获取配置文件的本地副本:
appservices pull --remote=<App ID> 默认,该命令会将文件提取到当前工作目录中。 您可以使用可选的
--local
标志指定目录路径。为数据库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" } } } } 现在将代码添加到trigger调用的
functions/config.json
函数中。函数
retryOperation
将重试处理程序发布到失败执行跟踪器集合的文档作为参数logEntry
。 然后,retryOperation
使用context.functions.execute()使用来自logEntry
的信息调用主函数。functions/config.json[ // ...other configuration { "name": "retryOperationDbTrigger", "private": true } ] 将以下代码添加到文件
functions/retryOperationDbTrigger.js
中:retryOperationDbTrigger.jsasync 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; 部署更改:
运行以下命令以部署更改:
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 ,添加以下代码:
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; 单击 Save(连接)。
将函数元数据添加到
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; 部署更改:
运行以下命令以部署更改:
appservices push
现在,在您调用 additionWithRetryHandler
时,如果失败,函数将重试。