Async keyword in TMS WEB Core (Leon Kassebaum)

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(const AValue: T): T; static;
    ...
end;

This allows you to write the following:

...
var
  lValue: TMySpecialType;
begin
  lValue := TAwait.Exec(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