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

 

 

 

 

 

 

 

 

Calling the apex method in LWC is asynchronous and it returns a promise.

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.

Important Links:

Salesforce
MDN