Unable to assign caught errors in try/catch block to a document field

I have an eventing function that utilizes many cURL calls, each of which I have wrapped in a try/catch block. When an error occurs, I successfully catch the error, and then create a new document with a number of fields to retry the failed cURL call with some linear backoff.

My issue arises when I create the new retry document. I have a field named ‘error’ who’s value is set to the ‘err’ caught in the catch block. The purpose of this is to give my team some visibility into what types of failures we’re getting. The problem is the value is never correctly assigned, and retry_doc[‘error’] always equals {} (empty object). I’m confused because if I simply log the error after its caught, I see the error in the log file correctly. But, when I try to assign the error to the retry document field, its always an empty object.

Thanks in advance for any help.

Hi - can you please post the code? That might be easier to understand. Is retryDoc an array (i.e retryDoc[‘error’]) or an object (i.e. retryDoc.error )

Yeah, here’s an example of a try/catch, which then logs and passes the error to queueRetry(). I’ve verified the error gets passed to queueRetry() successfully by calling log() within queueRetry. The errorDoc gets created successfully, but as mentioned errorDoc.error is always {}.

try {
const response = curl(‘POST’, scouting_note_delete_api, body);
log('Scouting Note Delete response: ', response);
handleMicroserviceApiResponse(
‘deleteScoutingNote’,
‘scouting_visit::’ + guid,
body.body,
response,
);
} catch (error) {
log(error);
queueRetry(
‘scouting_visit::’ + guid,
body,
‘deleteScoutingNote’,
error,
true,
);
}

function queueRetry(docId, requestBody, action, error, caughtError) {
const timestamp = Date.now();
const now = new Date();
const errorDoc = {
docId: docId,
requestBody: requestBody,
action: action,
error: error,
retryCount: 0,
caughtError: caughtError,
timestamp: now.toISOString(),
success: false
};

const error_doc_id = ${docId}::${timestamp};

retry_bucket[error_doc_id] = errorDoc;
}

retry_bucket[error_doc_id] = errorDoc;

Does that actually save errorDoc into retry_bucket? I’m not familiar with that. I’m only familiar with couchbase.insert(collection, meta, doc);

Are all the other fields present? If so, I would try using something other than the name ‘error’ as it might be a reserved word.

Yeah using the bucket alias as a javascript object works, I first became familiar with that syntax from the couchbase documentation. And yes all the other fields are present. I tried both using couchbase.insert() and changing the name ‘error’ to ‘testError’ to no avail.

What about saving error.message? Maybe the serializer doesn’t know what to do with an exception object.

What do you mean by saving? I’ve tried assigning the error to a new variable which didn’t work, if thats what you mean.

I used this (based on your code), and it worked fine.

function OnUpdate(doc, meta) {
    log("Doc created/updated", meta.id);
    
    try {
const response = curl('GET', 'http://localhost:8091/should_return_404'); // returns different exception
log('Scouting Note Delete response: ', response);
} catch (error) {
log(error);
queueRetry(
'scouting_visit::' + '123',
'body',
'deleteScoutingNote',
error,
true,
);
}

function queueRetry(docId, requestBody, action, error, caughtError) {
const timestamp = Date.now();
const now = new Date();
const errorDoc = {
docId: docId,
requestBody: requestBody,
action: action,
error: error,
retryCount: 0,
caughtError: caughtError,
timestamp: now.toISOString(),
success: false
};

const error_doc_id = `${docId}::${timestamp}`;

log(errorDoc);
my_bucket[error_doc_id] = errorDoc;
}
}

function OnDelete(meta, options) {
    log("Doc deleted/expired", meta.id);
}
{
  "docId": "scouting_visit::123",
  "requestBody": "body",
  "action": "deleteScoutingNote",
  "error": {
    "message": "Need a cURL binding as the second parameter",
    "stack": "Error\n    at OnUpdate (myfunc.js:6:18)"
  },
  "retryCount": 0,
  "caughtError": true,
  "timestamp": "2024-05-03T16:51:27.629Z",
  "success": false
}

Oh interesting, what couchbase version are you using? We’re on enterprise 7.1.2. Also, not sure if this would matter, but the errors we’re seeing are typically 503s.

I have 7.6.1 Enterprise.

I’m getting “Need a cURL binding as the second parameter” because I’m passing the wrong args to the curl. But that’s not relevant to your issue of not being able to populated the ‘error’ property. (503 is service not available - of whatever you’re calling in curl, I guess).

Agreed, the error itself shouldn’t matter. Could you perhaps try on 7.1.2 to see if its a version issue?

Also works in 7.1.2.

{
  "docId": "scouting_visit::123",
  "requestBody": "body",
  "action": "deleteScoutingNote",
  "error": {
    "message": "Need a cURL binding as the second parameter",
    "stack": "Error\n    at OnUpdate (myfunc.js:6:18)"
  },
  "retryCount": 0,
  "caughtError": true,
  "timestamp": "2024-05-03T20:19:02.655Z",
  "success": false
}

Interesting. Well I’m not sure what to do. I’m glad it works on your end, but I’m still not having any luck. I can successfully log the error immediately within the catch block scope, but any other operations on the error fail. If I log ‘typeof error’ I get ‘object’, however iterating over the erroor (Object.keys().foreach()…) or trying to JSON.stringify(error) both just produce {}. Same result if I pass it as a parameter.

If the service being called is returning a 503, then your response will have a status of 503. i.e. If I make a curl that requires a password, I get back a response with a 401 status begin logged like this:

2024-05-07T12:16:30.836-07:00 [INFO] "Scouting Note Delete response: " {"status":401,"headers":{"X-Permitted-Cross-Domain-Policies":" none\r\n","X-XSS-Protection":" 1; mode=block\r\n","X-Content-Type-Options":" nosniff\r\n","WWW-Authenticate":" Basic realm=\"Couchbase Server Admin / REST\"\r\n","Server":" Couchbase Server\r\n","Pragma":" no-cache\r\n","Date":" Tue, 07 May 2024 19:16:29 GMT\r\n","Expires":" Thu, 01 Jan 1970 00:00:00 GMT\r\n","X-Frame-Options":" DENY\r\n","Content-Length":" 0\r\n","Cache-Control":" no-cache,no-store,must-revalidate\r\n"},"body":null}

in your function that would be logged by the line

log('Scouting Note Delete response: ', response);

Then your handleMicoserviceApiResponse() is called, and if you end up in the catch(error) it’s because of an exception thrown by handleMicoserviceApiResponse()? An empty one, perhaps?

[ I had put dummy values in the curl arguments and curl was throwing an exception. After I changed the call to get a 401, there’s no exception thrown by curl, it must be coming from your handleMicoserviceApiResponse() ]