Search this site
Embedded Files
io.turbodsl
  • Home
    • Fundamentals
    • Default Mechanisms
    • Sync vs. Async Scopes
    • Asynchronous Results
    • Asynchronous Modes
    • Retry Pattern
    • Code Reusability
    • Collections
    • Performance Efficiency
io.turbodsl

Previous: Sync vs. Async Scopes

Next: Asynchronous Modes

Asynchronous Results

AsyncResult
Evaluating raw AsyncResults
Evaluating results using AsyncResultScope
Receiving each result using AsyncResultScope

AsyncResult

A class hierarchy wraps every possible result from an AsyncJobScope:

  • AsyncResult.Completed → if AsyncJobScope finished its execution.

    • AsyncResult.Completed.Success → if AsyncJobScope finished its execution successfully.

    • AsyncResult.Completed.Failure → if AsyncJobScope finished its execution but with an error.

  • AsyncResult.Cancelled → if AsyncJobScope was cancelled.

Every time an AsyncScope is executed, a List<AsyncResult> is created that collects all registered AsyncJobScope results in the same order each job was added.

Evaluating raw AsyncResults

This approach uses async expression that accepts only one trailing lambda and should be used only when low-level details are required.

Since sealed-classes are used to represent AsyncResult, it is quite simple to evaluate each one:

TurboScope.execute {

   async {

       // adding 3 jobs for execution

       asyncJob<Int>{...}

       asyncJob<Boolean>{...}

       asyncJob<Double>{...}

   }.let { r ->

       when (r[0]) {

           AsyncResult.Cancelled -> ...

           is AsyncResult.Completed.Success -> ...

           is AsyncResult.Completed.Failure -> ...

       }

       when (r[1]) {

           AsyncResult.Cancelled -> ...

           is AsyncResult.Completed.Success -> ...

           is AsyncResult.Completed.Failure -> ...

       }

       when (r[2]) {

           AsyncResult.Cancelled -> ...

           is AsyncResult.Completed.Success -> ...

           is AsyncResult.Completed.Failure -> ...

       }

   }

}

But using this traditional approach is quite demanding since when expressions are required for every single result.

To simplify this, io.turbodsl offers extension-functions for List<AsyncResult>:

  • List<AsyncResult>.allCompleted() → true when all items are AsyncResult.Completed.

  • List<AsyncResult>.anyCompleted() → true if contains at least one AsyncResult.Completed.

  • List<AsyncResult>.allSuccess() → true when all items are AsyncResult.Completed.Success.

  • List<AsyncResult>.anySuccess() → true if contains at least one AsyncResult.Completed.Success.

  • List<AsyncResult>.allFailure() → true when all items are AsyncResult.Completed.Failure.

  • List<AsyncResult>.anyFailure() → true if contains at least one AsyncResult.Completed.Failure.

  • List<AsyncResult>.allCancelled() → true when all items are AsyncResult.Cancelled.

  • List<AsyncResult>.anyCancelled() → true if contains at least one AsyncResult.Cancelled.

TurboScope.execute {

   async {

       // adding 3 jobs for execution

       asyncJob<Int>{...}

       asyncJob<Boolean>{...}

       asyncJob<Double>{...}

   }.let { r ->

       if (r.allSuccess()) {

           // all r items are succeses

       } else {

           // handler errors / cancellations

       }

   }

}

Similarly, extension-functions for AsyncResult simplify how to check the result state:

  • AsyncResult.isCompleted() → true if the result item is AsyncResult.Completed - either a Success or Failure.

  • AsyncResult.isSuccess() → true if the result item is AsyncResult.Completed.Success.

  • AsyncResult.isFailure() → true if the result item is AsyncResult.Completed.Failure.

  • AsyncResult.isCancelled() → true if the result item is AsyncResult.Cancelled.

Using these functions is the same as using a when expression:

TurboScope.execute {

   async {

       // adding 3 jobs for execution

       asyncJob<Int>{...}

       asyncJob<Boolean>{...}

       asyncJob<Double>{...}

   }.let { r ->

       when {

           r[0].isSuccess() -> {...}

           r[0].isFailure() -> {...}

           r[0].isCancelled() -> {...}

       }

       :

   }

}

So far all these functions provide mechanisms to check the state of an AsyncJobScope result. To access the related value, you must use specific attributes:

  • AsyncResult.Completed.Success → exposes attribute value, which references the actual return-value from the AsyncJobScope.

  • AsyncResult.Completed.Failure → exposes attribute error, which references the actual error / exception causing AsyncJobScope to fail.

  • AsyncResult.Cancelled → exposes nothing, since the AsyncJobScope was cancelled, thus, its result is undetermined.

TurboScope.execute {

   async {

       // adding 3 jobs for execution

       asyncJob<Int>{...}

       asyncJob<Boolean>{...}

       asyncJob<Double>{...}

   }.let { r ->

       when (val r0 = r[0]) {

           AsyncResult.Cancelled -> {

               // r0 does not provide any reference to the determine state

           }

           is AsyncResult.Completed.Success ->  {

               // r0.value -> requires casting

           }

           is AsyncResult.Completed.Failure ->  {

               // r0.error -> it's a Throwable

           }

       }

       :

   }

}

Putting everything together:

TurboScope.execute {

   async {

       // adding 3 jobs for execution

       asyncJob<Int>{...}

       asyncJob<Boolean>{...}

       asyncJob<Double>{...}

   }.let { r ->

       if (r.allSuccess()) {

           // Safe to cast to Success, since all are Successes

           // But, you need to know the data-type for each r[index]

           val r0 = ((r[0] as AsyncResult.Completed.Success).value as Int)

           val r1 = ((r[1] as AsyncResult.Completed.Success).value as Boolean)

           val r2 = ((r[2] as AsyncResult.Completed.Success).value as Double)

           // Process results

       } else {

           // handler errors / cancellations

           // At least one r[i] is an AsyncResult.Completed.Failure

       }

   }

}

This is still quite extensive and evaluating raw results should be done in specific scenarios. It is better to use async with AsyncResultScope.

Evaluating results using AsyncResultScope

Similar to the previous approach, but async expression using two lambdas, build and block (which is the trailing lambda):

TurboScope.execute {

   async<String>(

       build = {

           // "this" is an instance of AsyncScope

           // adding 3 jobs for execution

           asyncJob<Int>{...}

           asyncJob<Boolean>{...}

           asyncJob<Double>{...}

       },

   ) { ok, r ->

       // "this" is an instance of AsyncResultScope

       if (ok) {

           // Safe to cast to Success, since all are Successes

           // But, you need to know the data-type for each r[index]

           val r0 = (r[0].success() as Int)

           val r1 = (r[1].success() as Boolean)

           val r2 = (r[2].success() as Double)

           // Process results and return a String value

       } else {

           // handler errors / cancellations

           // At least one r[i] is an AsyncResult.Completed.Failure

           // Throw error, or return a String value

       }

   }

}

AsyncResultScope receives 2 arguments:

  • Boolean → true if all results are AsyncResult.Completed.Success.

  • List<AsyncResult> → a list containing all AsyncJobScope results.

Additionally, AsyncResultScope exposes 2 extension-functions:

  • AsyncResult.success() → should be used only when the result is a AsyncResult.Completed.Success.

  • AsyncResult.failure() → should be used only when the result is a AsyncResult.Completed.Failure.

Receiving each result using AsyncResultScope

This approach uses async expression that accepts between 2 and 10 jobs and it is the preferred method when the number of asynchronous-jobs is known and static.

There are 9 different async functions accepting 2 to 10 AsyncJobScopes:

TurboScope.execute<String> {

   async(

       // adding 3 jobs for execution

       job1 = asyncJob<Int>{...},

       job2 = asyncJob<Boolean>{...},

       job3 = asyncJob<Double>{...}

   ) { ok, r1, r2, r3 ->

       // "this" is an instance of AsyncResultScope

       if (ok) {

           // No need to cast to Success, and safe to call success()

           // No need to cast to data-type - each r-index "knows" the type to cast into

           val r0 = r1.success() // r0 is an Int

           val r1 = r2.success() // r1 is a Boolean

           val r2 = r3.success() // r2 is a Double

           // Process results and return a String value

       } else {

           // handler errors / cancellations

           // At least one r[i] is an AsyncResult.Completed.Failure

           // Throw error, or return a String value

       }

   }

}

AsyncResultScope receives n+1 arguments:

  • Boolean → true if all results are AsyncResult.Completed.Success.

  • r<index> → AsyncResult instance for each job.

Using the extension-function success() on each "r" does not require explicit casting.

Comparing the previous async versions, there's a huge advantage when the number of AsyncJobs is known.

Previous: Sync vs. Async Scopes

Next: Asynchronous Modes

Copyright 2024 © migueltt
Google Sites
Report abuse
Google Sites
Report abuse