How to Implement Continuations in Lightning

Let's walk through what continuations are, why you need them, and how to implement them step-by-step using a live API demo endpoint that simulates a delay.

What Are Apex Continuations?

Imagine you're at a coffee shop. You place your order (API request) and instead of waiting at the counter (synchronous callout), you go find a comfy seat. When your drink is ready, they call your name (callback method). That's basically how continuations work.

In technical terms, a continuation is an asynchronous callout mechanism in Apex that lets Salesforce park your request, continue processing other things, and then resume when the external call returns.

Sounds magical, right? Let's see it in action.

Why Use Continuations in Salesforce?

If you're thinking, "Why not just use regular Apex callouts?”, here's the kicker: Salesforce enforces strict limits. For synchronous requests, you're capped at 10 seconds for a callout. That's not a lot if you're hitting a sluggish API.

Continuations give you:

  • Up to 120 seconds for a callout.
  • Non-blocking execution - no freezing up your UI or server thread.
  • Seamless integration with Lightning Components and LWC.

Now, let's build it.

Step-by-Step Implementation

We'll use this handy endpoint that simulates a 10-second delay: https://forceshark.com/api/v1/hello-world/continuation-demo?delayMs=10000

Perfect for testing how continuations work without actually needing a slow backend.

Step 1: Create the Apex Continuation Method

Here's where the Apex magic happens. We'll define a controller that makes the callout and registers the callback.

Make sure the Apex class is marked @AuraEnabled and you specify continuation=true.

Here's the code:

/**
 * Demonstrates Salesforce Continuation technology for non-blocking HTTP callouts.
 * UI remains responsive while waiting for external API responses.
 */
public with sharing class ContinuationDemoController {
    
    // Continuation=true allows UI to stay responsive during long API calls
    @AuraEnabled(Cacheable = false Continuation = true)
    public static Object startContinuation() {
        Continuation con = new Continuation(40); // 40-second timeout
        con.continuationMethod = 'processResponse';

        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://forceshark.com/api/v1/hello-world/continuation-demo?delayMs=10000');
        req.setMethod('GET');
        req.setHeader('Content-Type', 'application/json');
        req.setTimeout(30000);

        con.addHttpRequest(req);
        return con; // Method exits immediately, callout happens asynchronously
    }

    // Called automatically when HTTP response is received
    @AuraEnabled(Cacheable = false)
    public static String processResponse(List<String> labels, Object state) {
        try {
            HttpResponse response = Continuation.getResponse(labels[0]);
            
            if (response.getStatusCode() == 200) {
                return response.getBody();
            } else {
                String errorMsg = 'HTTP Error ' + response.getStatusCode() + ': ' + response.getStatus();
                System.debug('HTTP Error: ' + errorMsg);
                return errorMsg;
            }
            
        } catch (Exception e) {
            System.debug('Exception in processResponse: ' + e.getMessage());
            return 'Error processing response: ' + e.getMessage();
        }
    }
}

Let's break it down:

  • startContinuation() sets up the continuation and registers processResponse() as the callback.
  • processResponse() receives the HTTP response and returns the body.

Boom - just like ordering your coffee and waiting for the barista to call your name.

Step 2: Call the Continuation from LWC

Next, let's hook this into a Lightning Web Component. You're not calling the endpoint directly from LWC - remember, Salesforce is very protective about callouts from client-side JavaScript. So the LWC will talk to Apex, and Apex handles the callout.

Here's the JS file:

import { LightningElement, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import startContinuation from '@salesforce/apexContinuation/ContinuationDemoController.startContinuation';

/**
 * Demonstrates Salesforce Continuation technology for non-blocking long-running API calls.
 * The UI remains responsive while waiting for external API responses.
 */
export default class ContinuationDemo extends LightningElement {    
    @track response = null;
    @track isLoading = false;
    
    handleStartContinuation() {
        this.isLoading = true;
        this.response = null;
        
        this.showToast(
            'API Call Started', 
            'Long-running request initiated. UI stays responsive!', 
            'info'
        );
        
        // Continuation allows UI to remain responsive during long API calls
        startContinuation()
            .then(result => {
                this.response = result;
                this.isLoading = false;
                
                this.showToast(
                    'API Call Successful! 🎉', 
                    'Response received. The UI never froze during the wait!', 
                    'success'
                );
            })
            .catch(error => {
                this.isLoading = false;
                const errorMessage = error?.body?.message || error?.message || 'Unknown error occurred';
                
                this.showToast(
                    'API Call Failed', 
                    `Error: ${errorMessage}`, 
                    'error'
                );
                
                this.response = `Error: ${errorMessage}`;
            });
    }
    
    showToast(title, message, variant) {
        const event = new ShowToastEvent({
            title: title,
            message: message,
            variant: variant,
            mode: variant === 'error' ? 'sticky' : 'pester',
            duration: variant === 'success' ? 5000 : 3000
        });
        this.dispatchEvent(event);
    }
}

Quick walkthrough:

  • When the button is clicked, handleStartContinuation() fires.
  • It calls the Apex method and waits for the callback response.
  • While it's waiting, we show a "loading"message - no frozen UI here!

Step 3: Add the HTML Template

Let's create the front-end part of our component. It's simple, clean, and gives you real-time feedback while the callout is in progress.

<template>
    <lightning-card title="Salesforce Continuation Demo" icon-name="custom:custom19">
        <div slot="actions">
            <lightning-badge label="Non-Blocking UI" variant="success"></lightning-badge>
        </div>
        
        <div class="slds-p-horizontal_medium slds-p-bottom_medium">
            <div class="slds-grid slds-wrap slds-gutters">
                <!-- Control Section -->
                <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-2">
                    <lightning-card title="API Call Control" variant="narrow">
                        <div class="slds-p-around_medium">
                            <p class="slds-text-body_regular slds-m-bottom_medium">
                                This demo showcases Salesforce Continuation technology that allows long-running API calls 
                                without freezing the user interface.
                            </p>
                            
                            <lightning-button
                                label="Start Long-Running API Call"
                                variant="brand"
                                onclick={handleStartContinuation}
                                disabled={isLoading}
                                class="slds-m-top_medium">
                            </lightning-button>
                            
                            <template if:true={isLoading}>
                                <div class="slds-m-top_medium">
                                    <lightning-spinner 
                                        alternative-text="Processing API request..." 
                                        size="small">
                                    </lightning-spinner>
                                    <p class="slds-text-color_weak slds-m-top_small">
                                        <lightning-icon icon-name="utility:clock" size="xx-small" class="slds-m-right_x-small"></lightning-icon>
                                        API request in progress... Notice the UI remains responsive!
                                    </p>
                                </div>
                            </template>
                        </div>
                    </lightning-card>
                </div>
                
                <!-- Results Section -->
                <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-2">
                    <lightning-card title="API Response" variant="narrow">
                        <div class="slds-p-around_medium">
                            <template if:true={response}>
                                <div class="slds-grid slds-gutters_x-small slds-m-bottom_medium">
                                    <div class="slds-col slds-no-flex">
                                        <lightning-icon 
                                            icon-name="utility:success" 
                                            size="small" 
                                            variant="success">
                                        </lightning-icon>
                                    </div>
                                    <div class="slds-col">
                                        <span class="slds-text-heading_small slds-text-color_success">
                                            Response Received Successfully
                                        </span>
                                    </div>
                                </div>
                                
                                <div class="slds-box slds-theme_shade slds-text-longform">
                                    <p class="slds-text-body_small" style="font-family: monospace; white-space: pre-wrap; word-break: break-word;">
                                        {response}
                                    </p>
                                </div>
                                
                                <div class="slds-m-top_small">
                                    <lightning-badge 
                                        label="Non-blocking call" 
                                        variant="success">
                                    </lightning-badge>
                                </div>
                            </template>
                            
                            <template if:false={response}>
                                <div class="slds-align_absolute-center slds-p-vertical_large">
                                    <lightning-icon 
                                        icon-name="utility:info" 
                                        size="medium" 
                                        variant="inverse"
                                        class="slds-m-bottom_small">
                                    </lightning-icon>
                                    <p class="slds-text-color_weak">
                                        Click the button to start an API call and see the response here
                                    </p>
                                </div>
                            </template>
                        </div>
                    </lightning-card>
                </div>
            </div>
            
            <!-- Technical Info Section -->
            <div class="slds-m-top_large">
                <lightning-card title="How Continuation Works" variant="narrow">
                    <div class="slds-p-around_medium">
                        <div class="slds-grid slds-wrap slds-gutters_small">
                            <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3">
                                <div class="slds-media slds-p-around_small slds-theme_shade">
                                    <div class="slds-media__figure">
                                        <lightning-icon icon-name="utility:clock" size="small"></lightning-icon>
                                    </div>
                                    <div class="slds-media__body">
                                        <div class="slds-text-body_small">
                                            <strong>10 Second Delay:</strong> API simulates long processing
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3">
                                <div class="slds-media slds-p-around_small slds-theme_shade">
                                    <div class="slds-media__figure">
                                        <lightning-icon icon-name="utility:touch_action" size="small"></lightning-icon>
                                    </div>
                                    <div class="slds-media__body">
                                        <div class="slds-text-body_small">
                                            <strong>UI Responsive:</strong> You can interact while waiting
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3">
                                <div class="slds-media slds-p-around_small slds-theme_shade">
                                    <div class="slds-media__figure">
                                        <lightning-icon icon-name="utility:salesforce1" size="small"></lightning-icon>
                                    </div>
                                    <div class="slds-media__body">
                                        <div class="slds-text-body_small">
                                            <strong>Continuation:</strong> Salesforce's async technology
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </lightning-card>
            </div>
        </div>
    </lightning-card>
</template>

What's happening here:

  • The button triggers the continuation.
  • While isLoading is true, it shows a "waiting"message.
  • Once the response comes back, it gets displayed below.

Simple, right? The user clicks a button, Apex does the heavy lifting, and the UI stays smooth.

Common Gotchas (and How to Avoid Them)

Every Salesforce dev knows that nothing ever works perfectly the first time. So here are a few pitfalls you might hit - and how to dodge them:

CORS Errors: You won't get CORS errors since the callout is server-side, but make sure your endpoint is in the Remote Site Settings.
Return Types: Your Apex method must return an Object for the continuation, and your callback must return something serializable (like a String or JSON).
Clean Up Your Cache: Don't use @AuraEnabled(cacheable=true) with continuations. They're dynamic and don't play well with caching.
Debug Logs: Use debug logs to monitor the continuation behavior. You'll see a "Continuation Request Queued"log entry.
Timeout Management: You can set a timeout (up to 120 seconds), but be mindful that Salesforce may still kill your session if it feels sluggish.

Bonus Tip: What If You Need to Handle Multiple Requests?

Salesforce allows you to add up to three callouts in a single continuation. Here's a little tweak to the original method if you want to call multiple APIs and wait for all of them to return.

Continuation con = new Continuation(40);  
con.continuationMethod = 'processResponse';

HttpRequest req1 = new HttpRequest();  
req1.setEndpoint('https://forceshark.com/api/v1/hello-world/continuation-demo?delayMs=10000');  
req1.setMethod('GET');  

HttpRequest req2 = new HttpRequest();  
req2.setEndpoint('https://another-api.com/endpoint');  
req2.setMethod('GET');  

con.addHttpRequest(req1);  
con.addHttpRequest(req2);

Then in your callback:

public static String processResponse(List<String> labels, Object state) {  
    HttpResponse res1 = Continuation.getResponse(labels[0]);  
    HttpResponse res2 = Continuation.getResponse(labels[1]);  
    return 'Res1: ' + res1.getBody() + ', Res2: ' + res2.getBody();  
}

When Should You Use Continuations?

Here's a litmus test:

  • ​ Long-running APIs (e.g., payments, external search services)
  • ​ Third-party integrations with known delays
  •  You want to avoid governor limits on sync callouts
  •  Super-quick APIs (under 10 seconds)? Regular callouts are fine.

Wrapping Up

Continuations in Salesforce are like a backstage pass for Apex callouts - they let you dodge strict limits and play nicely with long-running APIs. You get smooth, asynchronous behavior in your LWC, and your users never feel the wait.

By using Apex continuations alongside LWC, you're building scalable, performant, and user-friendly apps that handle real-world delays like a champ.

Next time your API takes a coffee break, let continuations do the waiting.

Ready to give your Apex callouts a speed boost? Go try it out with the demo endpoint, and watch your Lightning Web Components shine!