When handling errors in asynchronous functions, always return immediately after invoking a callback with an error to prevent subsequent code execution. This avoids the risk of calling callbacks multiple times or continuing execution paths that assume success.
Bad pattern:
function loadViaCredentialProcess(profile, callback) {
proc.exec(profile['credential_process'], function(err, stdOut, stdErr) {
if (err) {
callback(err, null);
}
// Problem: execution continues even after error callback
try {
var credData = JSON.parse(stdOut);
// More processing that might fail or call callback again
callback(null, credData);
} catch(e) {
callback(e);
}
});
}
Good pattern:
function loadViaCredentialProcess(profile, callback) {
proc.exec(profile['credential_process'], function(err, stdOut, stdErr) {
if (err) {
return callback(err, null); // Return immediately after error callback
}
try {
var credData = JSON.parse(stdOut);
// More processing that only happens on success path
callback(null, credData);
} catch(e) {
return callback(e); // Return after error in try/catch as well
}
});
}
For functions that can be called synchronously or asynchronously:
function createPresignedPost(params, callback) {
if (typeof params === 'function' && callback === undefined) {
callback = params;
params = null;
}
// Check for errors first, return early
if (!this.config.credentials) {
var error = new Error('No credentials');
if (callback) {
return callback(error);
}
throw error; // Throw for synchronous callers
}
// Success path only executes if no errors were found
var result = this.finalizePost();
return callback ? callback(null, result) : result;
}
This pattern creates clear separation between error and success paths, making code more maintainable and preventing hard-to-debug issues caused by multiple callback invocations or unexpected execution after errors.
Enter the URL of a public GitHub repository