# Migration Guide: Converting Async/Await to FX_SUSPEND ## Overview The FX framework prohibits the use of `async/await` in the main thread to maintain a fully synchronous, reactive programming model. Instead, we use the `FX_SUSPEND` pattern for handling asynchronous operations. ## Core Concept FX_SUSPEND allows functions to suspend execution when encountering a Promise, and the FX framework will automatically replay the function when the Promise resolves. ## Key Rules 1. **NO `async` keyword** - Remove all `async` function declarations 2. **NO `await` expressions** - Replace with FX_SUSPEND checks 3. **Check for Promises** - Always check if a value is a Promise before using it 4. **Throw FX_SUSPEND** - When encountering a Promise, throw it wrapped in FXSuspend ## Basic Pattern ### Before (Using Async/Await) ```javascript async function loadData() { const response = await fetch('/api/data'); const data = await response.json(); return data; } ``` ### After (Using FX_SUSPEND) ```javascript // Get FXSuspend from global (set by fx.ts) const FXSuspend = (globalThis as any).FXSuspend; function loadData() { const response = fetch('/api/data'); // Check if it's a Promise and suspend if needed if (response instanceof Promise) { throw new FXSuspend(response); } const data = response.json(); // Check again for the json() result if (data instanceof Promise) { throw new FXSuspend(data); } return data; } ``` ## Common Patterns ### 1. Simple Promise Handling ```javascript // BEFORE async function getValue() { return await somePromise; } // AFTER function getValue() { const result = somePromise; if (result instanceof Promise) { throw new FXSuspend(result); } return result; } ``` ### 2. Sequential Operations ```javascript // BEFORE async function processData() { const raw = await fetchData(); const processed = await transform(raw); return processed; } // AFTER function processData() { const raw = fetchData(); if (raw instanceof Promise) { throw new FXSuspend(raw); } const processed = transform(raw); if (processed instanceof Promise) { throw new FXSuspend(processed); } return processed; } ``` ### 3. Try-Catch Handling ```javascript // BEFORE async function safeOperation() { try { return await riskyOperation(); } catch (error) { console.error('Failed:', error); return null; } } // AFTER function safeOperation() { try { const result = riskyOperation(); if (result instanceof Promise) { // Wrap with error handling const handledPromise = result.catch(error => { console.error('Failed:', error); return null; }); throw new FXSuspend(handledPromise); } return result; } catch (error: any) { // Re-throw FX_SUSPEND if (error instanceof FXSuspend) throw error; // Handle other errors console.error('Failed:', error); return null; } } ``` ### 4. Promise.all Replacement ```javascript // BEFORE async function loadMultiple() { const [a, b, c] = await Promise.all([ fetchA(), fetchB(), fetchC() ]); return { a, b, c }; } // AFTER function loadMultiple() { const promises = Promise.all([ fetchA(), fetchB(), fetchC() ]); if (promises instanceof Promise) { throw new FXSuspend(promises.then(([a, b, c]) => ({ a, b, c }))); } return promises; } ``` ### 5. Conditional Async Operations ```javascript // BEFORE async function conditionalLoad(useCache: boolean) { if (useCache) { return getCached(); } return await fetchFresh(); } // AFTER function conditionalLoad(useCache: boolean) { if (useCache) { return getCached(); } const result = fetchFresh(); if (result instanceof Promise) { throw new FXSuspend(result); } return result; } ``` ### 6. Class Methods ```javascript // BEFORE class DataService { async getData() { const response = await fetch('/api'); return await response.json(); } } // AFTER class DataService { getData() { const response = fetch('/api'); if (response instanceof Promise) { throw new FXSuspend(response.then(r => r.json())); } const data = response.json(); if (data instanceof Promise) { throw new FXSuspend(data); } return data; } } ``` ## Special Cases ### 1. Event Handlers Event handlers that don't need to integrate with FX's reactive system can still use async: ```javascript // OK - Event handler isolated from FX button.addEventListener('click', async () => { const data = await fetch('/api'); console.log(data); }); ``` ### 2. Worker Threads Code running in Web Workers or Node.js worker threads can use async/await freely: ```javascript // In worker.js - OK to use async self.onmessage = async (event) => { const result = await processData(event.data); self.postMessage(result); }; ``` ### 3. Factory Functions Functions that create the initial Promise can remain async if needed: ```javascript // The factory can be async async function createDataPromise() { return await fetch('/api'); } // But the consumer must use FX_SUSPEND function useData() { const promise = createDataPromise(); if (promise instanceof Promise) { throw new FXSuspend(promise); } return promise; } ``` ## Testing Your Migration ### 1. Check for Promise Returns After migration, ensure your functions handle both synchronous and asynchronous cases: ```javascript function testableFunction() { const result = someOperation(); // Always check if it's a Promise if (result instanceof Promise) { throw new FXSuspend(result); } return result; } ``` ### 2. Verify Error Handling Ensure FX_SUSPEND errors are re-thrown: ```javascript try { // Your code } catch (error: any) { // CRITICAL: Always re-throw FX_SUSPEND if (error instanceof FXSuspend) throw error; // Handle other errors handleError(error); } ``` ### 3. Test Suspension and Replay The FX framework will automatically handle suspension and replay: ```javascript // This will suspend on first call if data is a Promise // FX will replay when the Promise resolves function getData() { const data = fetchData(); if (data instanceof Promise) { throw new FXSuspend(data); } return data; } ``` ## Common Mistakes to Avoid 1. **Forgetting to check for Promises** ```javascript // WRONG - Will fail if result is a Promise function bad() { const result = maybeAsync(); return result.value; // Error if result is Promise } // CORRECT function good() { const result = maybeAsync(); if (result instanceof Promise) { throw new FXSuspend(result); } return result.value; } ``` 2. **Not re-throwing FX_SUSPEND** ```javascript // WRONG - Swallows FX_SUSPEND try { someOperation(); } catch (error) { console.error(error); } // CORRECT try { someOperation(); } catch (error: any) { if (error instanceof FXSuspend) throw error; console.error(error); } ``` 3. **Using await in FX context** ```javascript // WRONG - async/await in FX plugin export async function plugin(fx) { await initialize(); } // CORRECT - Use FX_SUSPEND export function plugin(fx) { const init = initialize(); if (init instanceof Promise) { throw new FXSuspend(init); } } ``` ## Benefits of FX_SUSPEND 1. **Synchronous API** - All FX operations appear synchronous to the developer 2. **Automatic Replay** - FX handles Promise resolution and function replay 3. **Better Performance** - No async overhead in the main thread 4. **Predictable Behavior** - Deterministic execution flow 5. **Framework Integration** - Works seamlessly with FX's reactive system ## Conclusion Converting from async/await to FX_SUSPEND requires a mental shift but provides significant benefits in the FX framework. The key is to always check for Promises and throw FX_SUSPEND when encountered, letting the framework handle the asynchronous complexity.