JavaScript async/await in APEX
The following technique will change the way we write JavaScript very soon.
If you're into JavaScript, you're gonna want to understand async/await
.
Prerequisites
JavaScript Promises were introduced to solve many JavaScript problems such as the callback hell. async/await
builds on top of Promises to provide an easier handling of asyncronous functions.
You may understand the examples in this blog post, but here's a little background on synchronicity vs asynchronicity and JavaScript Promises: GHOST_URL/javascript-promises-in-apex/
Async
async
is simply a keyword you put in front of a function definition. Example:
async function doSomething() {
// do something asyncronous
return 'something';
}
Whenever you invoke doSomething()
, it will be treated asynchronously. An async function always returns a Promise. That promise will contain the value that you defined (something
in the case above). More on this later.
If you look at the following:
function start() {
console.log('start');
console.log(doSomething());
console.log('end');
}
start();
You might expect to get
start
something
end
But in reality you will get
start
end
something
That's because doSomething()
was launched asynchronously.
That annoys a lot of people, and it leads to some very ugly code to and it becomes a pain to maintain all your calls in sequential order.
But remember, asynchronous JavaScript is better for the performance of your code, so we have to stick with it.
Await
This is where the magic happens. When using await
, you're essentially telling your code to stop and wait for the result of the asynchronous function to complete before going any further.
await
pauses until the async
function is done. By doing so, it allows you to write a natural top to bottom code without relying on callbacks or Promises to simulate a synchronous process.
await
always expects a Promise from the async
function, and parses the result automatically.
Let's try the same example again:
async function doSomething() {
// do something asyncronous
return 'something';
}
async function start() {
console.log('start');
console.log(await doSomething());
console.log('end');
}
start();
Now, the result will be:
start
something
end
And that is because we're awaiting the function doSomething
to complete before continuing.
Note: await
can only be used within an async function.
Usage in APEX
Here's a demo application so you can run the demos yourself.
In the examples below, I am using an AJAX process called hang
which loops long enough to hang the process for X seconds (with a given parameter x01
). PL/SQL code is not relevant here.
Here's the JavaScript function that I will be using to call the AJAX process:
async function ajaxHang(seconds) {
return apex.server.process(
"hang", {
x01: seconds
}
);
}
Notice how I am NOT handling the success callback or the error handling here. More on this later.
Example 1)
This example invokes one AJAX process using async/await
async function example1() {
// invokes ajaxHang(1) which takes 1 second until it's done (PAUSE)
var result = await ajaxHang(1);
// prints the result to the region
console.log('example1', result);
}
Here we have waited a total of 1 second:
- 1 seconds for
ajaxHang(1)
Example 2
This example invokes two AJAX processes consecutively using async/await
async function example2() {
// invokes ajaxHang(1) which takes 1 seconds until it's done (PAUSE)
var result1 = await ajaxHang(1);
// prints the result to the region
console.log('example2', result1);
// invokes ajaxHang(2) which takes 2 seconds until it's done (PAUSE)
var result2 = await ajaxHang(2);
// prints the result to the region
console.log('example2', result2);
}
Here we have waited a total of 3 seconds for the sum of all AJAX calls
- 1 second for
ajaxHang(1)
- 2 seconds for
ajaxang(2)
Example 3
This example invokes two AJAX processes simultaneously using async/await
async function example3() {
// invokes result 1 and result 2 at the same time
var result1 = ajaxHang(1);
var result2 = ajaxHang(2);
// The results are printed in order as the AJAX calls return
console.log('example3', await result1);
console.log('example3', await result2);
}
Here we have waited a total of 2 seconds because ajaxHang(2)
took the longest to complete
- 1 second for
ajaxHang(1)
- 2 seconds for
ajaxHang(2)
Example 4
This example invokes X number of AJAX processes using async/await & Promise.all feature
async function example4() {
// invokes result1, result2 and result3 at the same time
var result1 = ajaxHang(1);
var result2 = ajaxHang(2);
var result3 = ajaxHang(3);
var results = await Promise.all([result1, result2, result3]);
// The results are printed all at once, when all AJAX calls have returned
console.log('example4', results);
}
Here we have waited a total of 3 seconds because of ajaxHang(3)
took the longest to complete:
- 1 second for
ajaxHang(1)
- 2 seconds for
ajaxHang(2)
- 3 seconds for
ajaxHang(3)
Error handling
Surprisingly, the correct way to handle errors within async/await is by using a good old try/catch
. The example from above could become:
async function doSomething() {
// do something asyncronous
return 'something';
}
async function start() {
try {
console.log('start');
console.log(await doSomething());
console.log('end');
} catch(e) {
console.error(e);
} finally {
console.log('Do something no matter what');
}
}
start();
Browser Support
Async/await in supported in all major browsers today, but it will most likely never be supported in IE.
Keep this in mind if you decide to use it.
It is also available from Node.js 7.
I am very excited about this. I think it's fabulous that we can finally write simple top to bottom JavaScript while benefiting from the asynchronous behavior.
I was initially inspired by Dan McGhan's post on async for Node.js. Good stuff!
Thoughts?