Options
All
  • Public
  • Public/Protected
  • All
Menu

Module context

Context objects are used to track execution flows in the module's implementation (or at other places). It enables:

  • meaningful reporting of what is going on (wrong or not) during module's execution (see also Journal)
  • understanding the bottleneck in terms of performances
  • Log broadcasting to multiple destinations

Context can be used in synchronous or asynchronous scenarios, the following example illustrates both usages (code is simplified to only keep the relevant points):

 class Module extends ModuleFlux{

     output$ : Pipe<unknown>

     constructor(params) {
         super(params)
             addInput({  
                 onTriggered: ({data, context}) => 
                     this.process(data, context)
             })
             this.addOutput({id:'output'})
     }
       
     process(data, context: Context) {

         let data1 = context.withChild( 'syncStep', () => syncStep(data, context) )
         ctx.info("Synchronous step was successful", data1)
         
         asyncStep(data1, context).subscribe( 
             (result) => {
                 this.output$.next(result);
                 // it is always the responsibility of the developer to close
                 // the context provided to 'onTriggered' 
                 context.end()
              };
             (error) => {    
                 ctx.error( error )
                 context.end()
             }
     }

     syncStep( data, context: Context){

         context.info("Starting sync step", data)   
         let value // = ... the result of some computations
         return value
     }

     async asyncStep( data: unknown, context: Context): Observable<any> {

         let ctx = context.startChild('async step')
         ctx.info("Starting async step, expect 10 values to come", data)   

         return from(
             // a source of async data, e.g. requests, multi-threaded computations, etc
             ).pipe(
             tap( (value) => ctx.info("got a value", value))
             take(10),
             reduce( (acc,e) => acc.concat(e), []) 
             tap( (acc) => { 
                 ctx.info("got all 10 values", acc)
                 ctx.end()
             })
         )
     }
}

Synchronous cases

The Context class provide the method withChild, the developer is not in charge to start or end the child context as it is automatically managed. If an error is thrown and not catched during the callback execution, the child context end (along with its parents), the error is reported in the child context, and finally the error is re-thrown.

Asynchronous cases

The Context class provides the method startChild & end. It is the developer responsibility to call these methods at the right time, and also to deal with eventual exceptions.

The natural representation of a context (as presented in flux-builder), is a tree view representing the tree structure of the function calls chain. The children of a context can be of two types:

Logs broadcasting

Context objects can includes broadcasting Context.channels$ to multiple destinations and with different filters/maps through LogChannel.

Context from modules' execution use 2 channels:

A note about user-context

The Context object also conveys the user-context (it is discussed along with Adaptor). The library can not automatically forward the user-context when output are sent: it is not always as meaningful as expected, and the asynchronous (possibly multi-threaded) nature of module's processes makes it close to impossible (at least we did not find a safe way of doing so 🤫).

This explains why, when emitting a value through an output pipe, you should explicitly pass back the context that has been provided to you at the first place (to the onTriggered callback).

Index

Generated using TypeDoc