Control flow

Flows are inherently asynchronous. Every step in a flow is executed asynchronously, however, the graphical representation abstracts the asynchronous nature, eliminating the callback hell.

The intuitive graphical representation of flows also makes it easy to build complex, asynchronous programs that is otherwise difficult to create and visualize in traditional programming. If the steps are wired in parallel, they will execute concurrently. If the steps are wired in a sequence, they will execute sequentially. To join two or more concurrent paths, simply join them together. All the joined steps will wait for each other to complete the execution before executing further. It's that simple!

The program flow can also be controlled by a transition's configuration, that will determine if the transition should happen or not during the runtime, depending on the current state. Different types of transitions such as normal, conditional, error, and always can decide the direction of program flow dynamically.

Steps in sequence

To execute steps in a sequential fashion, place them in a sequence. They will be executed one after the other. This is illustrated by the debug animation below:

sequential execution|width:550 Debugging a flow with steps wired in sequence. step2 will evaluate only after step1 completes the execution.

Steps in parallel

To execute steps in parallel, place them in parallel paths. Parallel steps are executed concurrently and do not wait for each other. Since they do not have connections between them, their output data cannot be accessed within each other. It makes sense as you cannot predict which step will complete first.

parallel execution|width:550 Debugging a flow with steps wired in parallel. step2 will evaluate simultaneously with step1.

Joining parallel paths

Joining parallel paths is really easy and intuitive in Codeflow. To join two or more paths, simply have transitions that converge on another step. That step will wait until all the joined paths completes their execution. This lets you build asynchronous business logic without using tedious programming constructs or libraries.

As you can notice in the below animation of debugger in action, the steps step1 and step2 are invoked concurrently, and step3 is invoked only after both of them completes.

joining paths|width:550 Animation of debugger in action. As you can notice, step1 and step2 starts the execution simultaneously. However, step3 waits until step2 and step1 complete the execution.

Conditional transition

As you have seen in the previous chapter, transitions can have conditions that determine the program flow during runtime. The below image shows an example of a conditional transition. The condition from start to step2 is hard coded with boolean false. This prevents that transition from triggering at the runtime. Since step3 is connected to step2 and no other steps, the transition from step2 to step3 also will not occur. The condition expression can be any Codeflow expression and can include data from the previous steps to decide the program flow based on the current state.

conditional transition|width:650 Above image is showing the transition from start to step2 with a condition set to false.

The below debug animation shows the above flow in action. As you can see, the execution does not reach step2 and step3. Also the transitions step2-step3 and step3-step4 are greyed out to convey that they do not occur.

conditional transition animation|width:650 Animation showing debugger in action. Transitions that are taken are highlighted in green, and the stale transitions are colored in grey.

Stale paths and joins

If a path is not taken due to a false condition, then the entire path consisting of steps and transitions that are linked only to that transition will also not be invoked. This will affect the control flow. Such a path is marked as stale and any joins that are waiting downstream won't wait for that path anymore.

Otherwise transition

There can be any number of conditional transitions from a step. The otherwise transition can be used to handle an else case where the transition should happen if none of the conditions evaluate to true.

The below animation shows two conditional transitions to handle two cases and an otherwise condition to capture every other cases. The otherwise transition will be triggered whenever there are no other successful transition.

multiple conditions|width:600 Animation showing usage of otherwise transition.

The above flow is roughly equivalent to the below pseudo logic:

function checkShapes(string shape) {
    if (shape == 'square')
        print "Square shape"
    elseif (shape == 'circle')
        print "Circle shape"
    else 
        print "unrecognized shape"    
}

Otherwise transitions can also be chained to perform nested conditions. The below image shows a usage of multiple conditional and otherwise transitions to perform a nested logic.

nested conditions|width:650 Image of a flow that uses multiple conditional and otherwise transitions to perform a nested check.

And its roughly equivalent pseudo code:

function checkAge(integer age) {
    if (age <= 30) {
        if (age < 18) {
            print "Age < 18"
        } else {
            print "Age between 18 and 30"
        }
    } else {
        print "Age greater than 30"
    }
}

Exception handling

Exceptions can also be handled through transitions. Transition types 'error' and 'always' can be used to channel the control flow when an exception occurs.

Error transition

Transitions with type set as error will capture any error thrown by the source step. The exception data is captured in the step's output and can be used by subsequent steps and transitions to control the program flow.

The below animation shows an error handling in action during a debug session:

error

The designer shows exceptions in a different color for easy visual identification. There can be more than one error transition - and all of them are taken during exceptions.

Always transition

The always transition will happen no matter what. It is sort of roughly equivalent to the finally clause of few procedural programming platforms. There can be any number of always transitions, and all of them are traversed always, irrespective of whether other transitions occur or not.

Unhandled errors

Errors occuring in the steps that do not have an outgoing error or always transition becomes unhandled errors and are bubbled up the stack. If an unhandled error occurs in the middle of a sub flow, it will be thrown as an error of that step in the parent flow and will continue to bubble up the chain until handled.

Multiple returns

A flow can have more than one end step. When there are two simultaneous end steps, only one will return successfully. The first end step to reach in the execution flow will succeed in setting the output value of the flow. All other end's return values are ignored.

When there are both an unhandled or manually thrown error and a normal end step, the return is taken on a first come first serve basis. Because of the asynchronous nature of execution, it is impossible to predict which step will return first on parallel paths. To prevent such ambiguous control flow, take extra care to design the flows that have parallel paths using error, always and conditional transitions and joins to ensure that the return happens in a predictable fashion.