JavaScript is by nature a synchronous language, which means functions are executed in the order they were written, or more simply put, functions can only be executed one at a time.
That point highlights one of the biggest downsides of standard JavaScript, i.e., each function must finish executing before the next one can start, causing what is known as blocking. Blocking can render a webpage slow and unresponsive. This is similar to when an SAP ABAP program specifies COMMIT WORK AND WAIT, where the program has to wait for the result of that commit before continuing on.
This is where asynchronous JavaScript comes in. Asynchronous JavaScript is a way for you to get around the linear flow of standard JavaScript and add some flexibility to your code, so you can either wait for a function to finish executing or let it execute in the background and finish in its own time. This is similar to an ABAP program called COMMIT WORK where the program doesn’t wait around for the result of the commit.
Note that this is an advanced JavaScript topic, but one worth being introduced to from the beginning. Asynchronous JavaScript is illustrated in the following listing with the JavaScript setTimeout() function, which pauses the function execution for a few seconds. Notice how the function callExternalServer() does not hold up the doSomethingElse() function (even though it is executed before) and lets the rest of the program continue until it completes.
const doSomething = () => {
console.log("Do something");
};
const callExternalServer = () => {
setTimeout(() => {
console.log("Call to external server complete");
}, 3000);
};
const doSomethingElse = () => {
console.log("Do something else");
};
doSomething();
callExternalServer();
doSomethingElse();
In this blog post, we’ll look at the older JavaScript callbacks, the JavaScript promise, and finally the newer async/await syntax, which gives much more granular control over when our JavaScript logic executes.
Callbacks in JavaScript
Callbacks are one of the older coding strategies in JavaScript but you will see them often. They involve passing a function as a parameter to another function. The idea is that you create your first function that runs until completion and when it does, it calls another function, known as the callback. Think of it as sending someone a message and asking them to do something and “call you back” when they are done.
This allows for more granular control over when functions are executed and allows you to handle scenarios where one function relies on another to complete before it can start. A great way to illustrate this is with a calculator. Below, we create a calculator we can pass some numbers into and when the calculation is done, we call another function that logs that result to the console.
const displayCalculatorResult = (result) => {
console.log(result);
};
const calculatorWithCallback = (num1, num2, callback) => {
let result = num1 + num2;
// Calling our callback function
callback(result);
};
calculatorWithCallback(1, 9, displayCalculatorResult);
Notice that we declared two functions, one to display the output and the other to calculate it. In our calculator function, we specified two number parameters and a third callback parameter, which is where we passed the display function. So, when we call the calculator function, the display function is only executed after the calculation has completed and logs the result to the console. The key here is that the display function only runs when the calculation is complete.
JavaScript Promise
The JavaScript promise is another advanced topic, but it is effectively just another way of writing a callback function with the added advantage of returning either a success or error and letting us decide what to do in each scenario. Promises are used for situations where you need to request data from outside your app and need to wait for that data to be returned before you start processing. We can simulate this using the setTimeout() function again, which will pause our promise execution and mimic a call to an external server.
The syntax for a promise is a bit complex, but it will make sense the more you use them. In the next listing, we declare a new function promise with resolve and reject parameters that we will use inside the function. We tell the function to return a resolve once the setTimeout function has completed after 5000 milliseconds (5 seconds).
const myFirstPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("The server finally sent a response!");
reject("An error occured :(");
}, 5000);
});
myFirstPromise
.then((data) => {
// resolve part
console.log(data);
})
.catch((error) => {
// reject part
console.log(error);
});
Notice how there is a five-second delay before the text is logged to the console. We call the myFirstPromise function and place the then() keyword after it, which holds the function to be executed when the promise function resolves. The resolve from the promise passes the text in the resolve brackets as a parameter to the then() part, which we import using the data parameter.
Also, notice that we have a reject() in there as well, but it was not executed. Normally, you would have some logic within the promise that would determine if the resolve were called or the reject. To trigger the reject in our example, we comment out the resolve() line and run the example again like before. You should now see the text "An error occurred :(" logged to the console.
So, when using promises, just remember that promise resolve maps to the then() section and promise reject maps to the catch() section (like a TRY/CATCH block in ABAP). You can structure your success or fail logic around that.
Async and Await
The async and await keywords in JavaScript help make writing asynchronous code easier, compared to the previous promise syntax. The async keyword is used to transform a JavaScript function into a promise and replaces the new Promise syntax from the listing above. This then allows you to use the await keyword within the async function, which when placed in front of a normal function will wait for that function call to finish.
In the next listing, we compare a normal function versus what happens when you insert async before it. Notice how myFunction() returns the text, while myAsyncFunction() returns text wrapped in a promise because of the async keyword.
const myFunction = () => {
return "A normal function";
};
console.log(myFunction()); // A normal function
const myAsyncFunction = async () => {
return "A promise function"; // Promise { 'A promise function' }
};
console.log(myAsyncFunction());
Now that we know myAsyncFunction is a promise, we can rewrite our standard promise example using the async syntax, as shown below. We will also add two more calls to see how the flow works before and after the setTimeout function runs.
const myAsyncFunction = async () => {
const myFirstPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("The server finally sent a response!");
// reject("An error occured :(");
}, 5000);
});
// Run this first
console.log("Let's get started!");
// Run this second but wait to finish (5 seconds)
console.log(await myFirstPromise);
// Run only when the function promise above has resolved
console.log("I had to wait for the call above to finish");
};
myAsyncFunction();
We have the following text logged to the console:
Let’s get started! (Followed by a five-second pause.)
The server finally sent a response!
I had to wait for the call above to finish!
The async function acts as a wrapper for you to call other functions (including normal promises) within it, giving you better control over where one function might need to wait on another before executing. To see the benefit of the await keyword, remove it completely and see the following logged to the console:
Let’s get started! (no five-second pause).
Promise { <pending> }
I had to wait for the call above to finish!
You can clearly see where the promise (or any other function that takes time to execute) sits and how it has not been given a chance to finish before proceeding onto the next line of code, resulting in potential instability. Add the await keyword in and the correct order of events would be restored.
Editor’s note: This post has been adapted from a section of the e-book JavaScript for ABAP Developers by Brandon Caulfield.
Comments