Wednesday, March 25, 2020

Small Framework for Server-Side Calls in Aura

Promises in Aura

In Aura, we spend a lot of time writing the same method over and over again: calling the server. Now, we can include a small script that calls the server and uses Promises to promote small, reusable code. This one method means that we only have to write about three lines of code to call the server, including error handling. This requires minimal adaptation, but it should make it far easier to write server calls in a consistent manner.

The Static Resource

We simply need a small piece of code that we will load in our components.

aura.js

window.server = function({component, method, params}) { return new Promise( $A.getCallback((resolve, reject) => serverSync({component:component, method:method, params:params,
success:resolve,
error:reject})) ); } window.serverSync = function({component, method, params, success, error}) { var action = component.get(method); params && action.setParams(params); action.setCallback(this, result => { switch (result.getState()) { case "DRAFT": case "SUCCESS": success.call(this, result.getReturnValue()); break; default: error.call(this, result.getError()); } }); $A.enqueueAction(action); }
These two simple functions allows us to write small code for all of our components. We can add further features later, but that is outside the scope of this post. For now, we are simply interested in calling the server as efficiently as possible.

Using in a Component or App

With this small script, we now need to write our components a little differently. Instead of handling the aura:valueInit handler like we used to, we now need to wait for our simple script to load. It is small and loads nearly instantly, so there should not be any visible lag time. Here is what a component should start off with now:
<aura:component controller="staticDemo"> <ltng:require scripts="{!$Resource.aura}" afterScriptsLoaded="{!c.init}" />

Calling the Server

Now that we have loaded our script, we can now call the server in several different ways.

Calling Once

In the most usual case, we can call the server with just one method and get the result.
server({component: component, method: "c.myMethod", params: {key: value}}) .then(result => component.set("v.someAttribute", result)) .catch(error => helper.displayError(error));

Calling Multiple at Once

Similarly, we can call more than one method at a time, and continue when we're done:
Promise.all([ server({component: component, method: "c.method1"})
.then(result => component.set("v.value1", result)), server({component: component, method: "c.method2"})
.then(result => component.set("v.value2", result)), server({component: component, method: "c.method3"})
.then(result => component.set("v.value3", result)) ]) .then(results => {}) .catch(error => helper.displayError(error));

Calling Multiple in Serial

Sometimes, we need the result of a previous value to call the next step. We can do this with a Promise chain:
server({component:component, method:"c.method1"}) .then(result => (component.set("v.value1", result),
server({component:component, method:"c.method2", params: {someValue: result}}))) .then(result => component.set("v.value2", result)) .catch(error => helper.displayError(error));

Supporting Cacheable Methods

Cacheable (a.k.a. Storable) methods may end up calling the resolve/reject method more than once. This is not compatible with Promises. If you find yourself in a situation where you need to call those methods, use the serverSync method. While it is not actually a synchronous method, it bears a different name to remind you that you need to pass in your own success and error handlers, and that you cannot chain them with promises. This is still arguably more legible than writing the same fifteen or so lines of code every time.
serverSync({component:component, method:"c.serverAction",
success: result => component.set("v.value1", result),
error: error => helper.displayError(error)});

The Commas

You might notice the use of (param) => (operation1, operation2) in the examples above. This design is used to take advantage of the comma operator. I feel that this is marginally more legible than using code blocks ({}). With the comma operator, the function on the left is evaluated and the results are discarded, then the function on the right is called. This allows us to chain Promises together while storing the intermediate results back to our component. Do not forget the parentheses, as they are necessary to make sure the comma operator works as we want to.

Conclusion

I found this pattern to be immensely useful, and I wanted to share it with others, as well as document this for my future self, should I ever forget to use it.