A deep dive into synchronous vs asynchronous script execution in enterprise FileMaker solutions


The Challenge: One Button, Multiple Dependencies

Picture this: Your user clicks a single “Set Preferred Supplier” button. Behind the scenes, your FileMaker solution needs to:

  1. Toggle the supplier’s preferred status
  2. Cascade that change to all related supplier provisions
  3. Recalculate sort orders for both suppliers and provisions tables
  4. Refresh the user interface

Simple enough, right? But here’s the catch: order matters. Get the sequencing wrong, and you’ll have data inconsistency that’s nearly impossible to debug.

The Real-World Example

Let me show you a real script orchestration that handles this exact scenario. This isn’t theoretical—it’s production code managing thousands of supplier records in a manufacturing environment.

The Master Script: One Button Rules Them All

# setPreferedSupplier - The orchestrator script

# Step 1: Toggle the preference
If [ suppliers::_isPrefered = 0 or suppliers::_isPrefered = "" ]
    Set Field [ suppliers::_isPrefered; 1 ]
Else 
    Set Field [ suppliers::_isPrefered; 0 ]
End If

# Step 2: CASCADE UPDATE (BLOCKING - Critical!)
Set Variable [ $JasonPayload; Value:JSONSetElement ( "{}" ;
    [ "table" ; "supplierProvisions" ; JSONString ] ;
    [ "targetField" ; "_isPrefered" ; JSONString ] ;
    [ "foreignKey" ; "_fkSupplierID" ; JSONString ] ;
    [ "targetValue" ; suppliers::_isPrefered ; JSONNumber ]
) ]

Perform Script on Server [ "refreshRelatedTableTrigger_Server"; Parameter: $JsonPayload ]
[ Wait for completion ]  # ← THE KEY DECISION!

# Step 3: SORT OPERATIONS (NON-BLOCKING - Performance!)
Perform Script on Server with Callback [ "setSupplierCustomSortOrder_Server"; 
    Callback script: "EmptyCallback"; State: "Continue" ]

Perform Script on Server with Callback [ "setSupplierProvisionsCustomSortOrder_Server"; 
    Callback script: "EmptyCallback"; State: "Continue" ]

# Step 4: Refresh UI
Refresh Portal [ Object Name: "prtl1" ]

The Critical Decision: When to Block vs When to Flow

Notice the difference in execution patterns:

BLOCKING Execution (Wait for completion)

Perform Script on Server [ "refreshRelatedTableTrigger_Server" ]
[ Wait for completion ]

NON-BLOCKING Execution (Callback with Continue)

Perform Script on Server with Callback [ "setSupplierCustomSortOrder_Server"; 
    Callback script: "EmptyCallback"; State: "Continue" ]

Why the difference? It all comes down to data dependencies.

Understanding Data Dependencies

The Dependency Chain

  1. suppliers._isPrefered changes from 0 → 1
  2. supplierProvisions._isPrefered must update to match (via foreign key relationship)
  3. Sort operations must use the UPDATED preference values

If step 2 doesn’t complete before step 3 begins, the sort operations will use stale data, creating an inconsistent user experience.

The Blocking Strategy

For the cascade update, we use blocking execution:

Perform Script on Server [ "refreshRelatedTableTrigger_Server" ]
[ Wait for completion ]  # Don't continue until cascade finishes

This ensures that ALL related supplierProvisions records have their _isPrefered field updated before any subsequent operations begin.

The Non-Blocking Strategy

For the sort operations, we use non-blocking callbacks:

Perform Script on Server with Callback [ "setSupplierCustomSortOrder_Server"; 
    Callback script: "EmptyCallback"; State: "Continue" ]

Since sorting suppliers and sorting provisions are independent operations (neither depends on the other), they can run in parallel for better performance.

The Universal Cascade Engine

The real magic happens in the universal cascade updater. Instead of writing specific scripts for every table relationship, we built a JSON-driven engine:

# refreshRelatedTableTrigger_Server
Set Variable [ $params; Value:SafeJSONParse(Get(ScriptParameter)) ]
Set Variable [ $table; Value:JSONGetElement($params; "table") ]
Set Variable [ $targetField; Value:JSONGetElement($params; "targetField") ]
Set Variable [ $foreignKey; Value:JSONGetElement($params; "foreignKey") ]
Set Variable [ $fieldValue; Value:JSONGetElement($params; "fieldValue") ]
Set Variable [ $targetValue; Value:JSONGetElement($params; "targetValue") ]

Go to Layout [ $table ]
Enter Find Mode [ ]
Set Field By Name [ $table & "::" & $foreignKey; $fieldValue ]
Perform Find [ ]

Loop [ Flush: Always ]
    Set Field By Name [ $table & "::" & $targetField; $targetValue ]
    Go to Record/Request/Page [ Next; Exit after last ]
End Loop

This one script handles ANY one-to-many relationship cascade. Just pass it the right JSON parameters:

{
  "table": "supplierProvisions",
  "targetField": "_isPrefered", 
  "foreignKey": "_fkSupplierID",
  "targetValue": 1
}

Performance vs Consistency: The Balancing Act

When to Use Blocking (Wait for completion)

Data dependencies exist
Subsequent operations need the results
Data consistency is critical
User expects immediate feedback

When to Use Non-Blocking (Callbacks)

Operations are independent
Performance matters more than immediate completion
User doesn’t need to wait for background processes
System has multiple concurrent operations

The Architecture Principles

1. Separate Concerns

  • Client scripts: UI orchestration only
  • Server scripts: Data operations only
  • Universal engines: Reusable business logic

2. JSON-Driven Configuration

Instead of hardcoding table/field relationships, use JSON parameters for maximum flexibility.

3. Transaction Safety

Always wrap related operations in transactions:

Open Transaction
# ... do work ...
Commit Transaction

4. Error Handling

Every server-side operation should include proper error trapping and logging.

Real-World Results

This orchestration pattern handles:

  • 400+ suppliers with instant preference toggling
  • 2,000+ supplier provisions with automatic cascade updates
  • Sub-second response times even with complex data relationships
  • Zero data inconsistency issues in production

The Takeaway

Modern FileMaker development isn’t just about writing scripts—it’s about orchestrating experiences. Understanding when to block vs when to flow is crucial for building systems that are both performant and reliable.

The next time you’re designing a complex user interaction, ask yourself:

  1. What are the data dependencies?
  2. Which operations must complete before others begin?
  3. Which operations can run in parallel?
  4. How can I make this reusable for future features?

Master these concepts, and you’ll build FileMaker solutions that feel magical to users while remaining maintainable for developers.


Want to dive deeper into FileMaker architecture patterns? The key is understanding that great user experiences often require sophisticated orchestration behind the scenes. One button click can trigger a symphony of coordinated operations—the art is in making it look effortless.

About the Author
This article was inspired by real-world enterprise FileMaker development challenges and the ongoing evolution of client-server architecture patterns in modern business applications.