Skip to Content
Technical Articles

Writing asynchronous code in UI5 using ‘async functions’ (part of ES8)

“A new way to call functions synchronously.”

Introduction

Why this blog

The concept of ‘async functions’ is one of the latest available JavaScript features which is already widely adopted (especially in the node.js scene). With async functions we can synchronously run multiple JavaScript functions without the use of call-back-functions or using ‘then()’ chains. This means the second function waits for the first function to be finished. This without blocking the UI.

However, to use this method in the SAP WebIDE, at least you’ll need to add a dependency in package.json to make it work. Therefore I feel the urge to write this blog.  Also I would like to plead for the use of this feature to all the early adopters in the UI5 scene.

Async functions should not be confused with asynchronous loading of UI5 components.

Also, the fact that async functions enable us to call functions synchronous works confusing.

Resume

  • In this blog I show in detail how to use async functions.
  • Async functions are part of version 8 of JavaScript a.k.a. EcmaScript8, ES8 or ES2017.
  • Async functions are a cleaner alternative for using chains of ‘.then’ (ES6). Callback functions (ES5) should not be used anymore unless your customer is still on IE11 or lower.
  • Code calling Async functions is easy to read.
  • Within Async functions, it is the Await statement that makes calls to be done synchronously.
  • We have to keep in mind the power of JavaScript is in its asynchronous way of processing; And therefore, we have to use the await statement consciously.
  • It is possible to build/deploy async functions in SAP WebIDE with a little adjustment in package.json. There is also a more advanced solution available which enables other JavaScript Features as well.
  • Four given use cases in this blog are related to: push-notifications, OData-calls, time-outs and busy-indicator.
  • The finally section after a try/catch still gives problems in the WebIDE.

My final advise to SAPUI5 developers is described in my last word as follows:

“I would like to encourage all readers and leaders to spread the word to use async functions, but with consciousness and with the remark WebIDE users can’t build/deploy code containing the finally statement.” 

Syntax

How the Asynchronous functions look like

Under here you will see a very simple example of an asynchronous function that shows an alert after two seconds.

waitAndInform: async function () {
    await new Promise(function(resolve){setTimeout(resolve, 2000)});
    alert('You have waited 2 seconds');
},

In the example above you see two important commands: ‘async’ and ‘await’. In an async function the await command waits until a function that returns a promise is being resolved. This without blocking the application.

Because Async functions can only be declared at the top level or inside a block, the correct way to test this in your browser’s console is as follows:

async function waitAndInform() {
    await new Promise(function(resolve){setTimeout(resolve, 2000)});
    alert('You have waited 2 seconds');
}

Enter this.waitAndInform(); to test the async function.

Note: To make code testable in your browser’s console From now on I will not use this function declaration anymore and focus on the await statement. All code below can be tested in your browser’s console. If you would like to download the code examples wrapped in an working application for the WebIDE, visit this GIT:  https://github.com/Hilderink/asyncFunctions_UI5.

What the alternative look like

To understand the conciseness of the async functions I would like to compare it with older ways to solve this problem.

Let’s first visualise a function that returns a value after 2 seconds. In the next chapter, I will give a few more complex and representative examples of promises to be returned.

//Prepare by building a function returning a promise
function resolveAfter2Seconds(x) {
    return new Promise(function(resolve){
        setTimeout(function(){
            resolve(x);
        }, 2000);
    });
}

Under here you will see another example of an asynchronous function to be compared with the ES6 variant.

//ES8 variant
var r = await this.resolveAfter2Seconds('Example 1');
console.log(r);// returns Example 1 after 2 seconds
//ES6 variant
this.resolveAfter2Seconds('Example 2')
.then(function(r){
    console.log(r);// returns Example 2 after 2 seconds
});

As you can see, the new variant is shorter and officially there is nothing wrong with the ES6 variant. But as further you will read this blog you will probably understand better how much cleaner the code can get when your application gets more advanced.

Note: ES8 stands for the 8th version of JavaScript (officially called ECMA-Script). ES8 is launched in 2017 and is carried by all modern browsers. ES6 is launched in 2015 and contained a huge load of new features for JavaScript. The only common browser where ES6 and ES8 are not supported is Internet Explorer. Internet explorer was Microsoft’s predecessor of Edge and even competed against Netscape in the previous century 😉. Internet Explorer supported most functionality of ES5, but not much more than that.

As an addition of this chapter I would like to compare the async functions with the ES5 variant, where I have to use call-back-functions.

//ES8 variant
var a = await this.resolveAfter2Seconds("Example ");
var b = await this.resolveAfter2Seconds("3");
console.log(a + b); // returns Example 3 after 4 seconds

To make the ES5 variant work we have to declare an alternative for the function ‘resolveAfter2Seconds’. This is because ‘resolveAfter2Seconds’ returns a promise, but before ES6 we had to work with call-back-functions.

//Prepare by building a callback function
function callbackAfter2Seconds(x, f) {
    setTimeout(function(){
        f(x);
    }, 2000);
}
//ES5 variant
var _this = this;
this.callbackAfter2Seconds('Example ', function(x){
    _this.callbackAfter2Seconds(x + '4', function(x){
        console.log(x);//returns Example 4 after 4 seconds
    });
});

In my opinion, Call-back-functions should not be built anymore since the introduction of Promises.

Use cases

In the examples above, I constantly used a function that resolves a promise based on a timeout. I would like to share with you 4 interesting use cases where promise objects are being returned.

Also external Library’s you use can be written in a way it returns a promise.

Important note: We have to keep in mind the power of JavaScript is in its synchronous execution model; And therefore, we should use async functions consciously.  

Push-notifications

In any modern browser, the method Notification.requestPermission(); will return a promise that is resolved after the user allows push-notifications.

permission = await Notification.requestPermission();
if(permission=="granted"){
    var notification = new Notification( 'Vladimir Hilderink says: ', {body: 'Example 5'});
}
if(permission=="denied"){
    alert('Unblock push notifications in your browsers settings and restart your browser');
}


OData calls

In a more common example the browser waits for the server to come with data.

You might want to build your model.js file in a way to return a promise. Here is an function that receives data from the server written in a way to return a promise:

function getExample6(){
    return new Promise(function(resolve, reject) {
        $.ajax({url: "Example6.txt",
            success: function(result){
                console.log('Call answered by server'); //Second text in console
                resolve({data: 'This was Example 6'});
            },
            error: function(request,status,errorThrown){ 
                console.log(status); 
                resolve({data: 'Example 6 returns an error'});
            }
        });
        console.log('Call made') //First text in console
    });
}
var d = await this.getExample6();
console.log(d.data); //Last text in console
Note: This might not work directly in your browsers console. 

Timeouts

The shortest way I have found to let the application wait for a second would be:

await new Promise(r => setTimeout(r, 2000));

Here we use an arrow-function, but I would prefer the way it is showed directly after the introduction.

Busy indicator

A busy indicator can be stopped after the function call is being done.

sap.ui.core.BusyIndicator.show();
await this.resolveAfter2Seconds();
sap.ui.core.BusyIndicator.hide();
Note: This won’t work directly in your browsers console.

Get it work in WebIDE

Enable async functionality in SAP WebIDE

At the moment of publishing this article, our new code will not be able to be deployed via the WebIDE. You will get the error below.

While just building the new code you will get another error:

Simple solution:

This happens because the default devDependencies refer to an old version of grunt. We will have to update the devDependencies in Package.json. Add the following line:

"grunt-openui5": ">=0.15.0"

This makes sure the WebIDE will take version 0.15.0 as a minimum to build the application. When SAP decides to make version 0.15.0 or higher as a default, your application will go back to default since  the ‘>=’ symbol is used.

This way you make async functions available in the WebIDE.

One of the main contributors to grunt-openui5 (Matthias Oßwald) is strongly related to SAP and therefore I would consider this solution without doubts.

Advanced solution:

If you want to make more features available like ‘let’, arrow functions and some calculation functions. Follow the more advanced steps as described in https://blogs.sap.com/2017/11/01/next-generation-javascript-in-ui5-using-sap-web-ide/. In this blog async functions are not described, but I have tested it by following the three simple steps resulting in a positive result.

The author of this article (Wouter Lemaire) is quite strong related to SAP as well. I would consider this solution without doubts as well.

In general:

In my research, I didn’t find any doubts for using any of these solutions above. I would rather to go for the advanced solution since it solves much more common problems than the first one.

Lesson learned: One error message for multiple common mistakes in SAP WebIDE

If you do any of the following tasks wrong, you would get the error below in your webIDE’s console while trying to build your project. An error showed in the previous paragraph pops up.

  • You did not implement at least one of the steps as described in the previous paragraph (‘Enable async functionality in SAP WebIDE’).
  • You declared a normal function using ‘function()’ instead of ‘async function()’, but you do use the await statement.
  • You only updated Package.json, but you used other unsupported features like arrow functions.

Additional arguments to use it (or not)

Try, Catch and Finally

Another outcome of a promise can be a ‘reject’. ‘Resolve’ applies for the situation with no errors. When in a promise an error appears the ‘Reject’ command should be called. In ES8 this can be catched with a ‘try/catch’.

//ES8 variant
try{
    var data = await this.resolveAfter2Seconds('Example 7');
    console.log(data); //returns Example 7 after 2 seconds
}
catch(err)
{
    console.error('Error: ', err)
}
//ES6 variant
this.resolveAfter2Seconds('Example 8').
then(function(data) {
    console.log(data); //returns Example 8 after 2 seconds
}).
catch(function(err) {
    console.error('Error: ', err)
});

The advantage of this way of catching errors is that it is more close to the way other languages handle it. Besides this, it is possible to write multiple functions in the try section and handle it with only one catch.

Important note: There should also be a finally section allowed that runs regardless of the result.  This finally statement can not be build/deployed in the webIDE with any of the given solutions. In this situation you have to create the chain of ‘.then()’, ‘.catch()’ and ‘.finally()’ like availble in ES6.   

Debugging

You can step over and into the function call. This is in most cases the same with a ‘.then()’ chain. The difference is within Arrow-functions, which is out of scope of this blog.

The advantage of then functions is there where you can see what is going to happen within the then-function, since it is not a separate function by default.

Extra hints to use it

Extra1: running asynchronous functions in parallel

We can run multiple functions in parallel like this:

var a = this.resolveAfter2Seconds('Extra ');
var b = this.resolveAfter2Seconds('1');
console.log( await a + await b ); //returns Extra 1 after 2 seconds

Extra2: Synchronous looping

If we want to loop over a function call and wait after each step, we solve that this way:

for (var val of ['Extra 2a...', 'Extra 2b']) {
    console.log(await this.resolveAfter2Seconds(val));
}

Extra3: Asynchronous looping

When we want to loop over a few function calls and do them in parallel, we use Promise.all or Promise.race. Promise.all waits for all the results to be resolved. Promise.race will wait for the first result to be resolved.

var p = [];
for (var val of ['Extra 2a', 'Extra 2b']) {
    p.push(this.resolveAfter2Seconds(val));
}
var values = await Promise.all(p);
console.log(values);

The end

Conclusion

In this blog I plead for the use of async functions with the async/await statements. Older alternatives are to chain then-methods which looks less concise, or to pass a call-back-function to another function. The understanding of Promises became an important part of JavaScript/SAPUI5. Functions that return a ‘promise’ are getting more common and are not only used in the communication to the server.

The way functions run in parallel and the way we can loop over function calls look very clean within async functions. Even the try-catch functionality will make code more understandable for other developers.

Async functions are part of ECMA-Script 8 (a.k.a. ES8 or ES2017). They are available to be used on the latest versions of all modern browsers. These browsers can update automatically. ES8 does not work on Internet Explorer, users of IE have to upgrade to Microsoft Edge or an alternative browser.

Developers using the SAP WebIDE have to execute some separate tasks. A more advanced solution is also available on the web.

Even though the finally method is not available in the SAP WebIDE, I would say this feature is a good improvement. The code stays shorter, cleaner and better understandable.

Thanks to

There was one question on sap.com that triggered me to write this blog. In the question below James Whitworth asks how to deploy ES6-featured code on the WebIDE on SCP.

https://answers.sap.com/questions/623191/webide-deploy-to-scp-does-not-work-with-es6.html

Thanks also goes to Ivan Mirisola for participating on SCN with answering to James.

Thanks also goes to Wouter Lemaire since I saw a lot of posts from him during my research on the web. Ivan also refers to Wouters blog: https://blogs.sap.com/2017/11/01/next-generation-javascript-in-ui5-using-sap-web-ide/. After testing out his blog I found out his solution also works for async functions. In the the paragraph ‘Enable this functionality in SAP WebIDE’ I refer to his solution. Another blog from Wouter which might be cool to read after this blog might be: https://blogs.sap.com/2018/09/17/ui5-odata-requests-with-promises/.

Thanks also goes to Boghyon Hoffmann, who gave the answer on the question that was marked as the correct answer. I used his solution in the paragraph ‘Enable this functionality in SAP WebIDE’. Boghyon also referred to this: https://stackoverflow.com/questions/49237874/const-and-let-declarations-in-ui5/52841062#52841062.

Also I want to thank my Ordina-colleagues Lieske van den Berg and Hilco Broens for reviewing the concept of my blog.

Last word

Thanks also goes to you for reading this blog.

I would like to encourage all readers and leaders to spread the word to use async functions, but with consciousness (since the power of JavaScript is in it synchronous execution model) and with the remark WebIDE users can’t build/deploy code containing the finally statement.

Vladimir Hilderink

Be the first to leave a comment
You must be Logged on to comment or reply to a post.