Understanding Async/Await
Instead of writing complex .then() chains, async/await lets you write code that looks simple and sequential.
- async → defines an asynchronous function
- await → pauses execution until the result is returned
In simple terms: “Wait for this result, then continue.”
Step 1: The Problem with Promises (Before Async/Await)
Let’s say you are calling an Apex method.
Old Way (Promises)
getAccounts()
.then(result => {
console.log(result);
})
.catch(error => {
console.error(error);
});
Issues:
- Hard to read when logic grows
- Nested chaining (messy)
- Error handling scattered
Step 2: Clean Approach with Async/Await
Better Way
async fetchAccounts() {
try {
const result = await getAccounts();
console.log(result);
} catch (error) {
console.error(error);
}
}
Same logic, but:
- Cleaner
- Easier to debug
- More readable
Step 3: Using Async/Await with Apex in LWC (Full Example)
Now let’s build a proper real-world example step by step.
Apex Controller
public with sharing class AccountController {
@AuraEnabled(cacheable=true)
public static List getAccounts() {
return [SELECT Id, Name FROM Account LIMIT 10];
}
}
LWC JavaScript (Step-by-Step)
import { LightningElement } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
export default class AccountList extends LightningElement {
accounts = [];
error;
async connectedCallback() {
await this.loadAccounts();
}
async loadAccounts() {
try {
this.accounts = await getAccounts();
} catch (err) {
this.error = err;
console.error('Error:', err);
}
}
}
What’s Happening Here
- connectedCallback() runs when component loads
- We call an async method loadAccounts()
- await getAccounts() waits for Apex response
- Data is assigned only after it is received
Important point: Even though we use await, the UI does NOT freeze.
Step 4: Important Rule
This will NOT work:
Apex Controller
connectedCallback() {
const data = await getAccounts(); // ❌ error
}
Because await only works inside an async function
Correct Way
async connectedCallback() {
const data = await getAccounts();
}
OR better:
connectedCallback() {
this.init();
}
async init() {
this.accounts = await getAccounts();
}
Step 5: Handling Multiple Calls
Sometimes you need multiple API calls.
Wrong Way (slow execution)
const data1 = await getData1();
const data2 = await getData2();
Runs one after another (slow)
Better Way (Parallel Execution)
async loadMultipleData() {
try {
const [data1, data2] = await Promise.all([
getData1(),
getData2()
]);
console.log(data1, data2);
} catch (error) {
console.error(error);
}
}
Both calls run together → faster
This is a common pattern used in real projects.
Step 6: Error Handling (Clean Way)
Sometimes you need multiple API calls.
async fetchData() {
try {
const data = await getAccounts();
this.accounts = data;
} catch (error) {
this.error = error;
}
}
Benefits:
- Centralized error handling
- Cleaner than .catch()
Step 7: Real UI Example (Loading State)
isLoading = true;
async loadAccounts() {
try {
this.isLoading = true;
this.accounts = await getAccounts();
} catch (error) {
this.error = error;
} finally {
this.isLoading = false;
}
}
Why this matters,
- You can show spinner until data loads
- Improves user experience