# func — Function Chaining > `import { chainAsync, chainSync } from 'puffy-core/func'` > CJS: `const { func: { chainAsync, chainSync } } = require('puffy-core')` > Also available in snake_case: `chain_async`, `chain_sync` Chains multiple functions sequentially with automatic error propagation. Each function receives the previous function's result. Stops on first error. This module depends on `puffy-core/error` — it uses PuffyResponse internally. --- ## chainAsync Chains async functions. Each function receives the result of the previous one. ### Signature ``` chainAsync(...fns: Function[]) → Promise ``` Parameters: - `...fns`: Functions to chain. Each receives the previous function's return value. Returns PuffyResponse where: - On success: `[null, { data: [...allResults], value: lastResult }]` - `data`: Array of ALL intermediate results (one per function) - `value`: The last function's result (shorthand) - On failure: `[Array, null]` ### Example 1 — Basic chaining ```js const [errors, result] = await chainAsync( () => Promise.resolve(1), previous => Promise.resolve(previous + 1), previous => previous + 2 ) // errors = null // result.data = [1, 2, 4] ← results of each function // result.value = 4 ← last result ``` ### Example 2 — Functions can be sync or async ```js const [errors, result] = await chainAsync( () => 10, // sync — returns 10 previous => Promise.resolve(previous * 2), // async — returns 20 previous => previous + 5 // sync — returns 25 ) // result.data = [10, 20, 25] // result.value = 25 ``` ### Example 3 — Error stops the chain If any function throws, the chain stops immediately: ```js const [errors, result] = await chainAsync( () => Promise.resolve(1), previous => { throw new Error('Boom') }, previous => previous + 100 // Never executed ) // errors = [Error('Boom')] // result = null ``` ### Example 4 — catchErrors inside the chain (PuffyResponse unwrapping) When a function returns a PuffyResponse (from catchErrors), chainAsync handles it specially: - If no errors: unwraps the data and passes it to the next function - If errors: stops the chain and wraps with its own error message ```js import { catchErrors } from 'puffy-core/error' // Success case — PuffyResponse is unwrapped const [errors, result] = await chainAsync( () => Promise.resolve(1), previous => catchErrors(Promise.resolve(previous + 1)), // returns PuffyResponse [null, 2] previous => previous + 2 // previous = 2 (unwrapped), NOT [null, 2] ) // result.data = [1, 2, 4] // result.value = 4 // Error case — chain stops with double-wrapped errors const [errors, result] = await chainAsync( () => Promise.resolve(1), previous => catchErrors(Promise.reject(new Error('Boom'))) ) // errors = [ // Error("'chainAsync' function failed"), ← added by chainAsync // Error('Boom') ← original error // ] // result = null ``` GOTCHA: Only PuffyResponse instances are unwrapped. If you return a plain array `[null, value]`, it is passed as-is to the next function (it is treated as a normal return value, not an error tuple). ### Example 5 — Direct throw vs PuffyResponse error ```js // Direct throw — single error const [errors] = await chainAsync( () => { throw new Error('Boom') } ) // errors = [Error('Boom')] // PuffyResponse with errors — double-wrapped const [errors] = await chainAsync( () => catchErrors(Promise.reject(new Error('Boom'))) ) // errors = [Error("'chainAsync' function failed"), Error('Boom')] ``` --- ## chainSync Identical to chainAsync but for synchronous functions only. Does NOT return a Promise. ### Signature ``` chainSync(...fns: Function[]) → PuffyResponse [errors, { data, value }] ``` ### Example ```js const [errors, result] = chainSync( () => 1, previous => previous + 1, previous => previous + 2 ) // errors = null // result.data = [1, 2, 4] // result.value = 4 ``` Note: Do NOT await chainSync — it returns synchronously. --- ## DO / DON'T Quick Reference ```js // DO: Await chainAsync const [errors, result] = await chainAsync(...) // DON'T: Forget await const [errors, result] = chainAsync(...) // errors is a Promise! // DO: Access result.value for the final result const [errors, result] = await chainAsync(fn1, fn2, fn3) if (!errors) console.log(result.value) // DON'T: Treat result as the direct return value const [errors, result] = await chainAsync(fn1, fn2, fn3) console.log(result) // This is { data: [...], value: ... }, not the final value // DO: Each function should accept the previous result const [errors, result] = await chainAsync( () => fetchUser(id), user => fetchOrders(user.id), // receives user orders => formatReport(orders) // receives orders ) // DON'T: Ignore the previous result parameter const [errors, result] = await chainAsync( () => fetchUser(id), () => fetchOrders(???) // Lost the user — no parameter accepted ) // DO: Use chainSync for synchronous functions (no await) const [errors, result] = chainSync(() => 1, prev => prev + 1) // DON'T: Use chainSync with async functions const [errors, result] = chainSync(async () => await fetch(...)) // The async function returns a Promise, which is passed as-is to the next function ```