The `AsyncResult` class in Xbase++ build# 2383 and higher provides a powerful framework for managing asynchronous operations with a standardized interface for handling both successful results and error conditions. This guide explains how to use the class effectively and covers its two primary usage patterns.
The `AsyncResult` class implements an asynchronous programming model similar to JavaScript Promises. It provides a way to:
This is the simplest pattern for using `AsyncResult` objects, where you trigger an asynchronous operation and then wait for and access its result.
You can specify a timeout (in milliseconds) when waiting for an operation to complete:
You can also use the `:get()` method directly, which implicitly calls `:wait()` if the operation is still in progress:
This pattern allows for more elegant chaining of operations and handling of results, similar to Promise chains in JavaScript.
In this example:
You can chain multiple `:then()` calls to process data in steps:
Each `:then()` codeblock receives the result of the previous operation and its return value becomes the input for the next step.
When an error occurs at any point in the chain, execution jumps to the `:catch()` handler:
The `:finally()` method registers a codeblock that executes regardless of whether the operation succeeded or failed:
The `:finally()` block doesn't receive any parameters and its return value is ignored.
If you're implementing a function that returns an `AsyncResult`, you should:
Chaining Existing Results: You can add handlers to an `AsyncResult` that is already completed. The handlers will execute immediately.
Error Propagation: Errors are automatically propagated through the chain until handled by a `:catch()` block.
Error Handling in Handlers: If a handler itself throws an error, that error will be propagated to the next `:catch()` handler in the chain.
State Transitions: An `AsyncResult` can only transition from "waiting" to either "finished" or "error". Once in a completed state, it cannot change.
Thread Safety: The `AsyncResult` class uses a `Signal` object internally to safely coordinate between threads.
The `AsyncResult` class provides a robust foundation for asynchronous programming in Xbase++. By using either the simple wait/get pattern or the more powerful then/catch/finally chaining pattern, you can write clean, maintainable code that effectively handles the complexities of asynchronous operations.
The `AsyncResult` class implements an asynchronous programming model similar to JavaScript Promises. It provides a way to:
- Encapsulate the result of an asynchronous operation
- Access the result when it becomes available
- Handle errors that may occur during the operation
- Chain multiple operations in a clean, readable syntax
- "waiting" - The operation is still in progress
- "finished" - The operation completed successfully
- "error" - The operation failed with an error
Usage Pattern 1: Wait/Get/Error/State
This is the simplest pattern for using `AsyncResult` objects, where you trigger an asynchronous operation and then wait for and access its result.
Basic Usage
Xbase++:
// Create an asynchronous operation that returns an AsyncResult
oResult := oSomeObject:someAsyncMethod()
// Wait for the operation to complete (blocks the current thread)
oResult:wait()
// Check the state
IF oResult:state() == "finished"
// Access the result
xValue := oResult:get()
// Use xValue...
ELSEIF oResult:state() == "error"
// Handle the error
oError := oResult:error()
// Process error...
ENDIF
Using Timeout
You can specify a timeout (in milliseconds) when waiting for an operation to complete:
Xbase++:
// Wait for up to 5 seconds
IF oResult:wait(5000)
// Operation completed within timeout
IF oResult:state() == "finished"
xValue := oResult:get()
// Process value...
ELSE
// Handle error
oError := oResult:error()
// Process error...
ENDIF
ELSE
// Timeout occurred, operation is still in progress
// Handle timeout...
ENDIF
Direct Access
You can also use the `:get()` method directly, which implicitly calls `:wait()` if the operation is still in progress:
Xbase++:
// Get the result (blocks if not ready)
xValue := oResult:get()
// Check if there was an error
IF oResult:state() == "error"
oError := oResult:error()
// Handle error...
ENDIF
Usage Pattern 2: Then/Catch/Finally with Codeblocks
This pattern allows for more elegant chaining of operations and handling of results, similar to Promise chains in JavaScript.
Basic Chaining
Xbase++:
oResult := oWebView:executeScript("document.title");
:then({|cTitle| "Page title: " + cTitle});
:catch({|oError| "Error: " + oError:description});
:finally({|| LogMessage("Operation completed")})
// Optionally wait for the entire chain to complete
oResult:wait()
In this example:
- The async operation fetches the document title
- On success, the `:then()` handler formats the title
- If an error occurs, the `:catch()` handler formats an error message
- The `:finally()` handler logs a completion message regardless of success or failure
Multi-step Data Processing
You can chain multiple `:then()` calls to process data in steps:
Xbase++:
oResult := oWebView:executeScript("document.links");
:then({|aLinks| Len(aLinks)}); // Count links
:then({|nCount| "Found " + AllTrim(Str(nCount)) + " links"});
:then({|cMessage| Log(cMessage),cMessage})
// Later access the final result
xFinalResult := oResult:get() // Contains the return value of the last then block
Each `:then()` codeblock receives the result of the previous operation and its return value becomes the input for the next step.
Error Handling
When an error occurs at any point in the chain, execution jumps to the `:catch()` handler:
Xbase++:
oResult := oWebView:executeScript("document.title");
:then({|cTitle| ProcessTitle(cTitle)}); // Might throw an error
:then({|xData| FurtherProcess(xData)}); // Skipped if error in previous step
:catch({|oError|LogError(oError)});
Finalization
The `:finally()` method registers a codeblock that executes regardless of whether the operation succeeded or failed:
Xbase++:
oResult := oDatabase:query("SELECT * FROM customers");
:then({|aResults| ProcessResults(aResults)});
:catch({|oError| LogError(oError)});
:finally({||CleanupResources()})
The `:finally()` block doesn't receive any parameters and its return value is ignored.
Creating AsyncResult Producers
If you're implementing a function that returns an `AsyncResult`, you should:
- Create a new `AsyncResult` instance
- Perform the asynchronous operation
- Call either `:signal()` with the result or `:signalError()` with an error
- Return the `AsyncResult` object immediately
Xbase++:
FUNCTION _asyncFetch( cURL, oResult )
LOCAL xData, oError
BEGIN TRY
// Perform operation
xData := HttpGet(cURL)
// Signal success with the result
oResult:signal(xData)
RECOVER USING oError
// Signal error
oResult:signalError(oError)
END
RETURN NIL
METHOD MyClass:fetchDataAsync(cURL)
LOCAL oResult := AsyncResult():new()
LOCAL oThread
oThread := Thread():new():start( {|cUrl, oResult |_asyncFetch( cURL, oResult )}, cURL, oResult )
// Return immediately, don't wait for the operation to complete
RETURN oResult
Advanced Considerations
Chaining Existing Results: You can add handlers to an `AsyncResult` that is already completed. The handlers will execute immediately.
Error Propagation: Errors are automatically propagated through the chain until handled by a `:catch()` block.
Error Handling in Handlers: If a handler itself throws an error, that error will be propagated to the next `:catch()` handler in the chain.
State Transitions: An `AsyncResult` can only transition from "waiting" to either "finished" or "error". Once in a completed state, it cannot change.
Thread Safety: The `AsyncResult` class uses a `Signal` object internally to safely coordinate between threads.
Conclusion
The `AsyncResult` class provides a robust foundation for asynchronous programming in Xbase++. By using either the simple wait/get pattern or the more powerful then/catch/finally chaining pattern, you can write clean, maintainable code that effectively handles the complexities of asynchronous operations.