Blog

All Blog Posts  |  Next Post  |  Previous Post

Async keyword in TMS WEB Core (Leon Kassebaum)

Bookmarks: 

Tuesday, January 26, 2021


Async? What's that?
Maybe you have already heard about the two concepts of synchronous and asynchronous processing of code statements. I give you an example. Let's have a look at a "normal" Delphi application and the following lines of code:
procedure MyProc();
var
  lResponse : string;
begin
  lResponse := ExecuteServerRequest;
  lResponse := 'Response: ' + lResponse;
  Writeln(lResponse);
end;
I think you know this type of application (maybe a VCL or FMX app) and of course line 6 is executed after receiving the servers response. This behavior is called synchronous because the computer performs the written code line by line.

If you write the same type of code in JavaScript things are working differently. Let me explain why. First of all pure JavaScript is single-threaded. Due to that fact it only has the possibility to perform your code line by line, so to say JS code is executed synchronously. But this only applies to JS and not for browser api calls. Imagine, if you perform an http request as I did in the example above, you call functions from the browser api. At this point the JS engine can continue with the next statement (next line of code) and the browser performs his task in the background. As soon as he is ready he gives you a callback. This is due to the fact that a website always has to react e.g. for resizing. If this would not be possible you would see an hourglass when connecting to a webserver (which lasts a bit) and your whole web app would be frozen. I think you know the well known Blue circle of death from windows.

The reason in JS style
So what to do if you want to connect to a webserver? The thing is that you have to wait for the response until you can continue your code. In JS the answer is splitted to three statements: async, await and promises. The concept behind this is that the promise which executes your code (e.g. pulling a webserver), has two callback functions. One is called if everything was successful and the other handles possible errors. Maybe it is better to give you a tiny example in JS:
...
let lPromise = new Promise(
  function(ASuccess, AFailed){
    setTimeout (
      function(){
        ASuccess("Waited 2 seconds") ;
      }, 2000);
    });
...

I created a promises which performs setTimeout() to wait for 2 seconds and then he calls the success function. The next step is that you can mark a function as async so that it returns a promise and no longer maybe a string. This
...
async function MyAsyncFunction(){
  return "Hello World!" ;
}
...

is the same as
...
async function MyAsyncFunction(){
  return new Promise(
    function(ASuccess, AFailed){
	  ASuccess("Hello World!");
	});
}
...

I know this is hard to understand but please keep in mind that this is just another way to return a promise but it is much more legible.

Calling this function with the await keyword makes sure that your code waits for the success callback function of the returned promise and calculates the result of this function which can be the response of the server. Think of the lPromise from above which simply waits 2 seconds. If I would run the following lines of code

console.log("Before calling");

  let lPromise = new ...

console.log(await lPromise);

it would first print Before calling to the console and wait 2 seconds in order to print Waited 2 seconds.

Available in TMS WEB Core?
Sure, this is a great feature of JS and should be possible in TMS WEB Core to build modern web apps. But it is not. So sorry, end of article.

No, just fun! Of course you can use it! The compiler magicians from the FPC community integrated this for you.

How to use
The use of async and await are very similar to its usage in JS. I will start with the async identifier. To convert a normal function to an async function you can do this:

function TMyObject.ThisMethodIsAsync: Integer; async;
begin
  ...
end;

or this, which is more readable and compilable by the Delphi compiler because he does not know the async keyword:
TMyObject = class(TObject)
  [async]
  function ThisMethodIsAsync: Integer;
end;

...

function TMyObject.ThisMethodIsAsync: Integer;
begin
  ...
end;

The mindblowing effect on this async keyword is that ThisMethodIsAsync no longer returns an Integer but now a TJSPromise! Remember, this is very similar to JS. It is also possible to create this promise manually. This way you can call the success callback function for returning your result. Technically this is the same but your code looks much more than Delphi code if you use async. Look at this:
function TMyObject.ThisMethodIsAsync: TJSPromise;
begin
  Result := TJSPromise.new(
    procedure(ASuccess, AFailed: TJSPromiseResolver)
    begin
      ASuccess(7);
    end);
end;

This example just returns the value (7). One problem could be that now you cannot share this source code between Delphi (maybe VCL) and TMS WEB apps because there is no TJSPromise in the VCL. Well, now there is a tiny base for async calls because the promise has a success callback function. But how to get the Result of async functions? Good question but as I said, this is very similar to JS, so we can use await. Here's the syntax:
procedure TMyObject.CallAsyncFunction;
var
  lBuffer: Integer;
begin
  lBuffer := await(ThisMethodIsAsync);
  ...
end;

In the lines below calling that async function, you can be sure that the result is stored in lBuffer and there won't be any callback function which returns later. If you want to have a look at more examples please visit the Pas2JS homepage.

Compiler magic
If you want to write and compile your code in the Delphi IDE you should pay attention that the dcc compiles your code (otherwise all of your code is marked red). Because of that the whole WEBLib exists twice. One implementation is the real one which is executed in the browser (Core Source) and the other one is for Delphi (Component Library Source). Of course you want to use programming assistance and to give Delphi a break you should give him valid Delphi code so that he can make suggestions. E.g. this is why I recommend you to use the async attribute. At this points the stub units in the "second" WEBLib are used because all functions from the real one are defined here as well but in valid Delphi syntax.

But anyway, let's have a concluding look at await. The thing is that Pas2JS recognizes async functions as functions which return a promise, but Delphi thinks that it returns e.g. a string or an Integer.
Well, the TMS team dealt with making await Delphi conform and defined it in the stub units like this:
function Await(const AValue: string): string; overload;
begin
  Result := AValue;
end;

As you can imagine this only works for a finite amount of types (in this case primitive types like string and Integer). So to say, if you create an async function which returns TMySpecialType the Delphi programming assistance crashes because await is not defined for it whether it compiles with Pas2JS. But the TMSWEBCompiler provides some compiler magic. It exists the following record in the unit JS.pas (since I mean the stub unit it is located in the Component Library Source folder, not in the Core Source folder):
TAwait = record
  public
    class function Exec<T>(const AValue: T): T; static;
    ...
end;
This allows you to write the following:
...
var
  lValue: TMySpecialType;
begin
  lValue := TAwait.Exec<TMySpecialType>(AsyncFunctionReturningThisType);
...

Internally this is only converted to
...
var
  lValue: TMySpecialType;
begin
  lValue := await(AsyncFunctionReturningThisType);
...

You should better use this generic method because this is compilable with Delphi and your programming assistance keeps working.

Conclusion
So to say, we have found a possibility to "synchronize" our code but keep in mind that it is still asynchronous! The huge advantage of this is that your code becomes very legible (you will thank yourself in the future) and you use the latest concepts of the JS engine, too!

Author: Leon Maximilian Kassebaum

Masiha Zemarai


Bookmarks: 

This blog post has received 2 comments.


1. Thursday, March 4, 2021 at 3:34:39 PM

Hi there,
I want to use web socket communication. Let me explain it with a simple example. I''ve a client and server. The client sends packets to the server. I want client to wait the response of server when client send a packet. Then client will do something according to response. Because of async working the client does not wait for response and do the next process. So isn''t there a function like onreceived() that I could use on the client side to make it to wait for server''s response
Thanks in advance


Mehmet Durmaz


2. Thursday, March 4, 2021 at 3:56:00 PM

A blog comment is not the place for technical support.
Visit https://support.tmssoftware.com/
For web socket communication, there is TWebSocketClient and it has an event OnDataReceived.

Bruno Fierens




Add a new comment

You will receive a confirmation mail with a link to validate your comment, please use a valid email address.
All fields are required.



All Blog Posts  |  Next Post  |  Previous Post