The Practical Client
Simple Asynchronous Processing for Web Service Calls
You don't need to write ugly code to synchronize your AJAX calls. Instead, you can leverage await/async and the JQueryXHR object to simplify the code around your AJAX calls while still getting the benefits of concurrent processing.
Here's some pseudocode I want to use when the user clicks my deleteCustomer button:
check that the user really wants to delete the customer ...
AJAX call to retrieve Customer object
.onsuccess (AJAX call to delete customer)
.onsucess(fix up the UI now that the customer is gone)
By using the jQuery ajax object to work with my server-side code, I get concurrent code: My UI code will execute concurrently with the series of AJAX calls. But ensuring that the second AJAX call won't execute until after the first call finishes has me nesting my second call in the first call's onsuccess method. I need a third nested call to handle my UI updates. If I wanted to do more in this process, I suspect my fourth call would have my code march off the right-hand side of the editing window.
I'm a big fan of really obvious code and, quite frankly, this isn't it. I'm going to be spending a significant period of time just making sure my parentheses and braces match up. What I want now is some simple code that, like this code, waits for the first call to complete and then either gives me access to the data from the earlier call if I want it, or lets me ignore the data if I don't.
In JavaScript and TypeScript, I can work with concurrent processing by returning a Promise object to the calling code. The Promise object can be returned from the method before the method completes to provide a way for the calling code to know when the long-running method finishes. I have to do some work inside the method with result and reject methods to support using the object, though.
When you catch a Promise object from a method you can pass it around, use it to retrieve the data returned by the long-running method or trigger follow-on processing. Of course, to retrieve the method's data, you have to wait for the method to complete. You also have to ensure that any "follow-on" code really does "follow-on" the method and doesn't overlap with it.
To support both of those needs, the Promise object has a method called "then" that allows you to retrieve the value produced by the method and trigger some follow-on processing when the method completes. Using the Promise object and its then method allows you to chain together sequences of tasks to be completed one after the other -- just what I want for retrieving and deleting my customer. However, I'm still going to be nesting code and beginning that inexorable march toward the right-hand edge of my window.
Simplifying Code with await and async
The async and await keywords eliminate that problem. They do not replace using Promise objects but do simplify working with them. For example, just applying the async keyword to a method or arrow expression causes it to return a Promise object -- no result or reject calls required. If the method is returning a value, that value is automatically wrapped in the Promise object.
In this example, the async keyword has been applied to a method that returns a Customer object. As a result, the return type of the method has become Promise<Customer>:
async method getCustomer(id: string): Promise<Customer> {
let cust: Customer;
//... code to load the cust variable ...
return cust;
}
An async method that doesn't return a value still returns a Promise object, so it must have its return type set to Promise.
Of course, any code that calls this method must accept its Promise object. Code that calls my getCustomer method, for example, would look like this:
let pCust: Promise<Customer>;
pCust = getCustomer();
To retrieve the value returned by the getCustomer method and process it, you would need to call the pCust object's then method, passing a call to removeCustomer. Follow-on code that uses the Promise object returned from getCustomer might look something like this:
pCust.then(c =>
{
removeCustomer(c);
})
As I said, I'm back to nested code.
Again, however, you can simplify the code by using the await keyword. The await keyword does two things: First, it pauses your code on the call to the async method until the Promise object is returned; second, it pulls out the value from inside the Promise object. Rewriting my previous code to use the await keyword gives code like this:
let cust: Customer;
cust = await getCustomer(id);
removeCustomer(cust);
As you can see, the variable holding my Customer object is no longer wrapped in a Promise object and no special code is required to wait for getCustomer to complete before calling removeCustomer.
I can keep extending these follow-on activities indefinitely. To manage the code that refreshes my page after the removeCustomer method finishes, I put the await keyword in front of my call to removeCustomer and put the refresh after it:
let cust: Customer;
cust = await getCustomer(id);
await removeCustomer(cust);
refreshPage();
The only wrinkle here is that any method that has code using the await keyword must also have the async keyword applied to it. To support that, I'd wrap this code up in a deleteCustomer method that looks like this:
async method deleteCustomer(id: string): Promise<void> {
let cust: Customer;
cust = await getCustomer(id);
await removeCustomer(cust);
refreshPage();
}
To compensate for that additional complexity, error handling with async and await also becomes easier. The interior of my deleteCustomer method would look something like this:
try {
let cust: Customer;
cust = await getCustomer(id);
await removeCustomer(cust);
refreshPage();}
catch (ex) { alert(ex.message); }
Working with Web Services
And, actually, even this code is too complicated because I'm working with jQuery's various AJAX calls. These methods all return a JQueryXHR object, which implements the Promise interface. As a result, if you're making an AJAX call using jQuery you can further simplify your code by eliminating the async keyword on any method making an AJAX call (no point in wrapping a Promise object in another Promise object). With an AJAX call inside of it, the getCustomer method just looks like this:
method getCustomer(id: string): JQueryXHR {
let xhr: JQueryXHR
xhr = $.get("/api/Customer/" + id);
return xhr;
}
The code that calls this method wouldn't need to change and could continue to use the await keyword when calling getCustomer.
As a variation on this plan, my deleteCustomer method doesn't have to be a method. If you prefer, you can just as easily use an arrow expression that does the same thing and store the expression in a constant. This code puts an async arrow expression in a constant called deleteCustomer:
const deleteCustomer = async (id: string): Promise<void> => {
try {
let cust: Customer;
cust = await getCustomer(id);
await deleteCust(cust);
refreshPage();}
catch (ex) { alert(ex.message); }}
Regardless of how I construct my deleteCustomer method, any code following the call to my deleteCustomer method will execute concurrently with the various AJAX calls retrieving and removing the customer. And, by eliminating those nested calls, my code is considerably easier to both read and write. That's a good thing.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.