Language Guide
Error Handling

Error Handling

Flowa supports structured exception handling with try, catch, and finally blocks for robust error management.

Basic Try-Catch

Handle errors gracefully with try-catch blocks:

try {
  let result = riskyOperation();
} catch (e) {
  print("Error: " + e);
}

Try-Catch-Finally

Use finally to ensure cleanup code always executes:

try {
  let file = openFile("data.txt");
  processFile(file);
} catch (e) {
  print("Error: " + e["message"]);
} finally {
  print("Cleanup executed");
}

Throwing Errors

Throw custom errors with the throw statement:

func validateAge(age) {
    if (age < 0) {
        throw "Age cannot be negative";
    }
    if (age > 150) {
        throw { "code": 400, "message": "Invalid age" };
    }
    return true;
}

Error Object Properties

When catching errors, the error object contains useful information:

try {
  validateAge(-5);
} catch (e) {
  // For runtime errors, e contains:
  // - e["message"] - error description
  // - e["type"] - error type (e.g., "RuntimeError")
  // - e["line"] - line number where error occurred
  print("Error at line " + e["line"] + ": " + e["message"]);
}

Error Object Properties:

  • message - The error message/description
  • type - The type of error (e.g., "RuntimeError")
  • line - Line number where the error occurred (when available)

Catching Structured Errors

Handle different types of errors:

try {
  let data = json.parse(invalid_json);
  let result = divide(10, 0);
} catch (e) {
  if (e["type"] == "ParseError") {
    print("JSON parsing failed: " + e["message"]);
  } else if (e["type"] == "RuntimeError") {
    print("Runtime error: " + e["message"]);
  } else {
    print("Unknown error: " + e["message"]);
  }
}

Practical Examples

File Operations with Error Handling

func readFileSafely(filename) {
    try {
        let content = fs.readFile(filename);
        if (content == null) {
            throw "File not found or could not be read";
        }
        return content;
    } catch (e) {
        log.error("Failed to read file: " + filename);
        log.error("Error: " + e["message"]);
        return null;
    }
}

Database Operations

func getUserById(db, id) {
    try {
        let result = sqlite.query(db, "SELECT * FROM users WHERE id = " + tostring(id));
        if (result == null || len(result) == 0) {
            throw { "code": 404, "message": "User not found" };
        }
        return result[0];
    } catch (e) {
        log.error("Database error: " + e["message"]);
        return null;
    } finally {
        // Cleanup if needed
    }
}

HTTP Request Error Handling

func handleRequest(req, res) {
    try {
        let data = json.parse(req.body);
 
        if (!("username" in data)) {
            throw { "code": 400, "message": "Missing username" };
        }
 
        // Process request
        res.writeHead(200, {"Content-Type": "application/json"});
        res.end(json.stringify({"status": "success"}));
 
    } catch (e) {
        let status_code = 500;
        if ("code" in e) {
            status_code = e["code"];
        }
 
        res.writeHead(status_code, {"Content-Type": "application/json"});
        res.end(json.stringify({"error": e["message"]}));
    }
}

Validation with Custom Errors

func validateUser(user) {
    if (type(user) != "hash") {
        throw { "type": "ValidationError", "message": "User must be a hash" };
    }
 
    if (!("email" in user)) {
        throw { "type": "ValidationError", "message": "Email is required" };
    }
 
    if (!contains(user["email"], "@")) {
        throw { "type": "ValidationError", "message": "Invalid email format" };
    }
 
    return true;
}
 
// Usage
try {
    validateUser({"name": "Alice"});  // Missing email
} catch (e) {
    if (e["type"] == "ValidationError") {
        print("Validation failed: " + e["message"]);
    }
}

Best Practices

1. Use Specific Error Messages

// Good
throw "Division by zero is not allowed";
 
// Better
throw {
  code: 400,
  message: "Division by zero is not allowed",
  field: "divisor",
};

2. Always Handle Errors

// Bad - unhandled error
let result = divide(a, b);
 
// Good - handle potential errors
try {
  let result = divide(a, b);
  print(result);
} catch (e) {
  print("Error: " + e["message"]);
}

3. Use Finally for Cleanup

let db = null;
try {
  db = sqlite.open("app.db");
  // Use database
} catch (e) {
  log.error("Database error: " + e["message"]);
} finally {
  if (db != null) {
    sqlite.close(db);
  }
}

4. Don't Swallow Errors

// Bad - error is hidden
try {
  riskyOperation();
} catch (e) {
  // Silent failure
}
 
// Good - log or handle error
try {
  riskyOperation();
} catch (e) {
  log.error("Operation failed: " + e["message"]);
  // Handle appropriately
}

Next Steps