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:

  1. Encapsulate the result of an asynchronous operation
  2. Access the result when it becomes available
  3. Handle errors that may occur during the operation
  4. Chain multiple operations in a clean, readable syntax
An `AsyncResult` object has three possible states:
  • "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:
  1. The async operation fetches the document title
  2. On success, the `:then()` handler formats the title
  3. If an error occurs, the `:catch()` handler formats an error message
  4. 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:

  1. Create a new `AsyncResult` instance
  2. Perform the asynchronous operation
  3. Call either `:signal()` with the result or `:signalError()` with an error
  4. Return the `AsyncResult` object immediately
Example:

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.