Asynchronous JavaScript
JavaScript is a single-threaded programming language. Execute the below function in your LWC (Lightning Web Components) component with a button click and check the console log. You will see that the code is executed line by line and doesn’t wait for the setTimeout asynchronous operation
import { LightningElement } from 'lwc'; export default class AsyncJs extends LightningElement { // Single Threaded behavior testSingleThreadedBehavior() { console.log("Sending request to server!") setTimeout(() => { console.log("Here is your data from the server...") }, 5000) console.log("I AM AT THE END OF THE FILE!") } }
JavaScript uses a call stack to manage the execution of functions, following the Last In, First Out (LIFO) pattern. When a function is called, it is pushed onto the call stack, and when the function completes, it is popped off the stack. In the code snippet below, the log statement for ‘thirdFunction’ will be displayed first on the console because it is added last to the stack
import { LightningElement } from 'lwc'; export default class AsyncJs extends LightningElement { // Callstack showCallstack() { this.firstFunction(); console.log('Inside call stack'); } firstFunction() { this.secondFunction(); console.log('Inside First function'); } secondFunction() { this.thirdFunction(); console.log('Inside Second Function'); } thirdFunction() { console.log('Inside Third Function'); } }
Problem Statement:
Create an LWC component to display contact and account records. The LWC component will include a button, when clicked, calls the loadData function. This will display the resulting contact and account records on the UI.
We will understand callbacks, promises, and Async/Await with the help of the above problem statement.
Callbacks
A callback is a function passed as an argument to another function, intended to be executed after the completion of some asynchronous operation or at a later time
There are two way we can write callback function
- Traditional way
- Arrow function
An arrow function in JavaScript is a better way to write function expressions. The primary difference is in the handling of the ‘this’ keyword.
In the below code, the ‘getAccountsRecords’ function has a callback written as an arrow function, whereas getContactsRecords is written in a traditional way.
getAccountsRecords will display account records on the UI, and we can access account properties inside the arrow function.
getContactsRecords will result in an error; we cannot access contact records using the traditional way of writing functions.
Error: Cannot set properties of undefined (setting ‘contacts’).
import { LightningElement } from 'lwc'; import getAccountRecords from '@salesforce/apex/AccountController.getAccountRecords'; import getContactRecords from '@salesforce/apex/AccountController.getContactRecords'; export default class Callback extends LightningElement { accounts; contacts; loadData() { getAccountRecords() // With Arrow function .then(data => { // 'this' refers to the instance of the component this.accounts = data; }) .catch(error => { console.error('Error: ', error.message); }); getContactRecords() // Without Arrow function .then(function(data) { this.contacts = data; }) .catch(error => { console.error('Error: ', error.message); }); } }
Promises and asynchronous
A Promises is an object representing the eventual completion or failure of an asynchronous operation.
It has three state
- Pending
- fulfilled
- rejected
import { LightningElement } from 'lwc'; import getAccountRecords from '@salesforce/apex/AccountController.getAccountRecords'; import getContactRecords from '@salesforce/apex/AccountController.getContactRecords'; export default class Promises extends LightningElement { accounts; contacts; checkIfPromise() { const result = getAccountRecords(); if (result instanceof Promise) { console.log("The function returns a Promise"); } else { console.log("The function does not return a Promise"); } } }
Promises have three important methods:
- then: This method is used to handle the eventual fulfillment or rejection of a promise.
- catch: This method is a shorthand for handling only the rejection of a promise.
- finally: This method is called when the promise is settled, whether fulfilled or rejected.
Check the code below where we use ‘then’ and ‘catch’ blocks to handle promises
import { LightningElement } from 'lwc'; import getAccountRecords from '@salesforce/apex/AccountController.getAccountRecords'; import getContactRecords from '@salesforce/apex/AccountController.getContactRecords'; export default class Promises extends LightningElement { accounts; contacts; loadData() { getAccountRecords() .then(result => { this.accounts = result; getContactRecords() .then(result => { this.contacts = result; // More nested callbacks can be added for additional operations }) .catch(error => { console.error('Error fetching contact records', error); }); }) .catch(error => { console.error('Error fetching account records', error); }); } }
The above code creates a nested structure, resulting in a pyramid-like structure, also known as Callback Hell. Promises allow us to chain ‘then’ blocks. Check the code below for an example of chaining ‘then’ blocks
import { LightningElement } from 'lwc'; import getAccountRecords from '@salesforce/apex/AccountController.getAccountRecords'; import getContactRecords from '@salesforce/apex/AccountController.getContactRecords'; export default class CallbackHell extends LightningElement { accounts; contacts; loadData() { getAccountRecords() .then(result => { this.accounts = result; return getContactRecords() }) .then(result => { this.contacts = result; }) .catch(error => { console.error('Error fetching records', error); }); } }
Async/Await
It provides a way to work with Promises more comfortably. In the code below, we have used the ‘async’ and ‘await’ keywords to work with promises. This is just another way to work with promises. Instead of using then/catch block we use Async/Await.
import { LightningElement } from 'lwc'; import getAccountRecords from '@salesforce/apex/AccountController.getAccountRecords'; import getContactRecords from '@salesforce/apex/AccountController.getContactRecords'; export default class AsynAwait extends LightningElement { accounts; contacts; async loadData() { try { this.accounts = await getAccountRecords(); this.contacts = await getContactRecords(); } catch (error) { console.error('Error fetching data', error); throw error; } } }
Conclusion
Calling an Apex method in LWC is an asynchronous operation. We have seen how code quality improves as we move toward the end of the block. We have also seen how it is important to know how the value of the ‘this’ keyword differs while writing an arrow function. The main key takeaway of this blog is that calling an Apex method returns promises, and we use the ‘then’ and ‘catch’ blocks to work with promises.