JavaScript
Async Programming
Callbacks
Asynchronous Functions
Await

add fixed parameter to await callback

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When using await with a function that accepts a callback-style argument, you often need to pass additional fixed parameters to that callback. In JavaScript, the standard techniques are closures, Function.prototype.bind(), arrow function wrappers, and partial application. The most common pattern is wrapping the callback in an arrow function that captures the fixed parameter from the enclosing scope. This works seamlessly with await because await simply resolves a Promise — how the Promise's internal callback receives parameters is a separate concern.

The Problem: Passing Fixed Values to Callbacks

javascript
1// You have an async function that takes a callback
2function fetchData(url, callback) {
3    // ... fetches data and calls callback(error, result)
4}
5
6// You want to call it with await and pass a fixed "format" parameter
7// But the callback signature is fixed: (error, result)
8
9// How do you get "format" into the callback?

Solution 1: Arrow Function Closure

javascript
1const format = 'json';
2
3// Wrap the callback-based function in a Promise
4function fetchFormatted(url, format) {
5    return new Promise((resolve, reject) => {
6        fetchData(url, (error, result) => {
7            if (error) return reject(error);
8            // 'format' is captured from the closure
9            resolve(processResult(result, format));
10        });
11    });
12}
13
14const data = await fetchFormatted('/api/users', 'json');

The arrow function (error, result) => { ... } closes over format from the outer scope. This is the most common and readable approach.

Solution 2: Using bind()

javascript
1function handleResult(format, error, result) {
2    if (error) throw error;
3    return processResult(result, format);
4}
5
6// bind prepends 'json' as the first argument
7const boundHandler = handleResult.bind(null, 'json');
8// boundHandler(error, result) → handleResult('json', error, result)
9
10function fetchWithBind(url, format) {
11    return new Promise((resolve, reject) => {
12        fetchData(url, (error, result) => {
13            try {
14                resolve(handleResult.bind(null, format)(error, result));
15            } catch (e) {
16                reject(e);
17            }
18        });
19    });
20}
21
22const data = await fetchWithBind('/api/users', 'json');

bind() creates a new function with preset arguments. The fixed parameter comes first, followed by the callback's own arguments.

Solution 3: Promisifying with Fixed Parameters

javascript
1import { promisify } from 'util';
2
3// Original callback-based function
4function readFile(path, encoding, callback) {
5    // callback(error, data)
6}
7
8// Promisify it
9const readFileAsync = promisify(readFile);
10
11// Now pass fixed parameters directly — await handles the rest
12const content = await readFileAsync('/path/to/file', 'utf-8');
13
14// For custom promisification with fixed parameters
15function fetchWithOptions(url, options, callback) {
16    // callback(error, result)
17}
18
19const fetchAsync = promisify(fetchWithOptions);
20
21// Fixed parameters go naturally before the (now-removed) callback
22const result = await fetchAsync('/api/data', { format: 'json', timeout: 5000 });

util.promisify removes the callback parameter and returns a Promise-based function. Fixed parameters are passed in their original positions.

Solution 4: Partial Application Helper

javascript
1// Generic partial application
2function partial(fn, ...fixedArgs) {
3    return (...remainingArgs) => fn(...fixedArgs, ...remainingArgs);
4}
5
6// Usage
7function processItem(format, retryCount, item) {
8    return { ...item, format, retryCount };
9}
10
11const processAsJson = partial(processItem, 'json', 3);
12// processAsJson(item) → processItem('json', 3, item)
13
14// With async/await in a loop
15const items = await fetchAllItems();
16const processed = items.map(processAsJson);
javascript
1// Partial application with named parameters (more readable)
2function createProcessor(options) {
3    return async function (item) {
4        const result = await transform(item, options.format);
5        if (options.validate) {
6            await validate(result);
7        }
8        return result;
9    };
10}
11
12const processJson = createProcessor({ format: 'json', validate: true });
13const result = await processJson(rawItem);

Real-World Example: Event Handlers with Fixed Context

javascript
1class DataProcessor {
2    constructor(apiBase) {
3        this.apiBase = apiBase;
4    }
5
6    async processAll(ids, format) {
7        // Fixed parameter 'format' passed to each async callback
8        const results = await Promise.all(
9            ids.map(id => this.processOne(id, format))
10        );
11        return results;
12    }
13
14    async processOne(id, format) {
15        const response = await fetch(`${this.apiBase}/items/${id}`);
16        const data = await response.json();
17
18        // 'format' is available via closure
19        return format === 'csv' ? toCSV(data) : data;
20    }
21}
22
23const processor = new DataProcessor('https://api.example.com');
24const results = await processor.processAll([1, 2, 3], 'csv');

TypeScript: Typed Fixed Parameters

typescript
1// Type-safe partial application
2function withFormat<T>(
3    fn: (format: string, item: T) => Promise<string>,
4    format: string
5): (item: T) => Promise<string> {
6    return (item: T) => fn(format, item);
7}
8
9async function serialize(format: string, data: object): Promise<string> {
10    if (format === 'json') return JSON.stringify(data);
11    if (format === 'yaml') return toYAML(data);
12    throw new Error(`Unknown format: ${format}`);
13}
14
15const toJson = withFormat(serialize, 'json');
16const result = await toJson({ name: 'Alice' });
17// result: '{"name":"Alice"}'

Common Pitfalls

  • Losing this context when using bind: bind() sets both this and fixed arguments. If you only want to fix arguments, pass null as the first argument to bind. But in class methods, you may need to pass the correct this or use arrow functions instead.
  • Stale closures in loops: When using closures inside for loops with var, all callbacks capture the same variable reference. Use let, const, or for...of to create a new binding per iteration, or pass the value through bind.
  • Confusing argument order with bind: bind prepends arguments. If the callback signature is (error, result) and you bind a format parameter, the callback becomes (format, error, result). Make sure the receiving function expects the fixed parameter first.
  • Promisifying functions that do not follow the Node callback convention: util.promisify assumes the last parameter is a callback of the form (error, result). Functions with different signatures need custom promisification using util.promisify.custom or a manual Promise wrapper.
  • Creating unnecessary closures in hot paths: Each arrow function creates a new closure object. In performance-critical loops processing millions of items, pre-bind the function outside the loop or use a shared callback to avoid garbage collection pressure.

Summary

  • Use arrow function closures to capture fixed parameters — the simplest and most readable approach
  • Use Function.prototype.bind(null, fixedArg) to prepend fixed arguments to any function
  • Use util.promisify to convert callback-based Node.js functions into Promise-based ones with natural parameter passing
  • Create partial application helpers for reusable parameter binding across multiple calls
  • In TypeScript, type the partial application wrapper to maintain type safety
  • Prefer closures over bind for readability, and bind over closures for reusable function references

Course illustration
Course illustration

All Rights Reserved.