Promise .then() chains often lead to nested, hard-to-read code. While async/await offers a cleaner alternative, migrating large codebases like Twilio’s Node.js SDK requires careful handling of backward compatibility.

Using Codegen, we performed this conversion reliably across all 592 instances.

Here is the conversion Pull Request made using Codegen.

The Pattern

Twilio’s Node.js SDK has this Promise chain pattern repeated across the codebase:

function update(callback?: (error: Error | null, item?: Instance) => any): Promise<Instance> {
  let operationPromise = operationVersion.update({
    uri: instance._uri,
    method: "post",
    headers,
  });

  operationPromise = operationPromise.then(
    (payload) => new Instance(operationVersion, payload)
  );

  operationPromise = instance._version.setPromiseCallback(
    operationPromise,
    callback
  );
  return operationPromise;
}

Each instance of this is found:

  1. Creating an operationPromise
  2. Transforming the response
  3. Handling callbacks through setPromiseCallback
  4. Returning the promise

Converting the Promise Chain to Async/Await

To perform this conversion, we used the Codegen convert_to_async_await() api.

Learn more about the the usage of this api in the following tutorial

Step 1: Finding Promise Chains

Extract all promise chains using the TSFunction.promise_chains method.

operation_promise_chains = []
unique_files = set()

# loop through all classes
for _class in codebase.classes:

    # loop through all methods
    for method in _class.methods:
    
        # Skip certain methods
        if method.name in ["each", "setPromiseCallback"]:
            continue

        # Only process methods with operationPromise
        if not method.find("operationPromise"):
            continue

        # Collect all promise chains
        for promise_chain in method.promise_chains:
            operation_promise_chains.append({
                "function_name": method.name,
                "promise_chain": promise_chain,
                "promise_statement": promise_chain.parent_statement
            })
            unique_files.add(method.file.filepath)

print(f"Found {len(operation_promise_chains)} Promise chains")
print(f"Across {len(unique_files)} files")
Found 592 Promise chains
Across 393 files

Step 2: Converting to Async/Await

Then we converted each chain and added proper error handling with try/catch:

for chain_info in operation_promise_chains:
    promise_chain = chain_info["promise_chain"]
    promise_statement = chain_info["promise_statement"]
    
    # Convert the chain
    async_code = promise_chain.convert_to_async_await(
        assignment_variable_name="operation"
    )
    
    # Add try-catch with callback handling
    new_code = f"""
        try {{
            {async_code}
            if (callback) {{
                callback(null, operation);
            }}
            return operation;
        }} catch(err: any) {{
            if (callback) {{
                callback(err);
            }}
            throw err;
        }}
    """
    promise_statement.edit(new_code)

    # Clean up old statements
    statements = promise_statement.parent.get_statements()
    return_stmt = next((stmt for stmt in statements 
        if stmt.statement_type == StatementType.RETURN_STATEMENT), None)
    assign_stmt = next((stmt for stmt in reversed(statements) 
        if stmt.statement_type == StatementType.ASSIGNMENT), None)

    if return_stmt:
        return_stmt.remove()
    if assign_stmt:
        assign_stmt.remove()

The Conversion

  • Converted 592 Promise chains across the codebase
  • Eliminated the need for setPromiseCallback utility
  • Maintained backward compatibility with callback-style APIs
  • Improved code readability and maintainability

Conclusion

Promise chains using .then() syntax often leads to complex and deeply nested code that’s harder to maintain. It’s an active problem that many teams want to pursue but never end up doing so due to the time consuming nature of the migration. Codegen can significantly accelerate these migrations by automating the conversion for several different cases.

Want to try this yourself? Check out our Promise to Async/Await tutorial