Blog

All Blog Posts  |  Next Post  |  Previous Post

#WEBWONDERS : Making web async easy

Monday, April 5, 2021

TMS Software Delphi  Components

Even these Delphi developers who have only set the very first steps into web client development will realize that quite a few important functionalities in web clients are executed only asynchronously by the browser. 

Why asynchronous?

First of all, why did the creators of browser implement certain functionality only asynchronously? The answer to this question is quite simple. For the developers behind the browser, the user experience comes first and a UI that freezes is a big no-go. At all times, the user interface should remain response. The user should never get the impression that the browser or the machine hangs while navigating on the internet. Now, several operations happening in the browser client application can by nature have a fairly unpredictable time to execute. So, no such function should stop the execution of user interface handling code in the browser. 

Traditionally, the browser developers solved this "issue" by JavaScript event handlers that are called asynchronously when the result of a certain operation is ready and meanwhile, any other code can continue to execute. This is no different in TMS WEB Core web client applications written in Object Pascal (but of course compiled to JavaScript by the pas2js compiler). Here, we typically solved this with the Object Pascal event paradigm. When a class instance method is invoked that can only have its result asynchronously, this class instance triggers an event handler when the underlying function completed. When the pas2js compiler also introduced support for anonymous methods, we offered for various such asynchronous operations, anonymous result handlers. To focus on this, let's take the example of a TWebHttpRequest class. This class permits to invoke HTTP(s) requests from the web client application. Clearly, this is an operation with an unpredictable duration, hence, the result of the request should be asynchronously handled.

Classic approaches

Example 1: handling via an event:

// method starting the HTTP request
procedure TForm1.WebButton1Click(Sender: TObject);
begin
  WebHttpRequest1.URL := 'https://www.tmssoftware.com/sample.json';
  WebHttpRequest1.Execute();
end;

// TWebHTTPRequest event handler for OnResponse
procedure TForm1.WebHttpRequest1Response(Sender: TObject; AResponse: string);
var
  jo: TJSONObject;
  jv: TJSONValue;
begin
  jo := TJSONObject.Create;
  try
    jv := jo.ParseJSONValue(AResponse)
    // do any further processing here on the parsed JSON
  finally
  end;
end;

Example 2: handling via an anonymous method:

procedure TForm1.WebButton1Click(Sender: TObject);
begin
  WebHttpRequest1.URL := 'https://www.tmssoftware.com/sample.json';
WebHttpRequest1.Execute( procedure(AResponse: string; ARequest: TJSXMLHttpRequest) var jo: TJSOBject; jv: TJSONObject; begin jv := TJSONObject.Create; try jv.ParseJSONValue(AResponse); // do any further processing here on the parsed JSON finally end; end); end;

Introducing promises

Now, while the implementation using anonymous methods probably leads to fairly readable code, imagine needing to handle with multiple HTTP requests that depend on each other, which is in web development a fairly common use-case. While you can in theory invoke another http request from the anonymous method handler that is in turn also handled by a new anonymous method handler, you can see that it quickly becomes clumsy.

In the JavaScript world, a more elegant solution was introduced in 2015 with the ES6 standard and by now, any modern browser adopted this and supports it. The good news is that the pas2js v2.0 compiler also embraced and adopted JavaScript promises and offers a Pascal styled equivalent. If you want to read all about JavaScript promises, there are many good resources, but this is a well-written and concise one. In a nutshell, with pas2js v2.0, we can decorate a method with the async keyword or attribute and when having done so, it allows to use the await() function to (asynchronously) wait for a result of a function returning a promise. When the function is successful, it returns the promises, when not, it triggers an exception we can handle. By using this await(), we can further write our code as if the execution was sequential (i.e. synchronous) and our brain seems to be programmed to understand this kind of logic much better. 

Now, in TMS WEB Core v1.7, we have already introduced in many components functions returning promises where you can use at application level, await() constructs to handle the code 'sequentially'. An example of this is the TWebHttpRequest.Perform: TJSPromise function that now allows to perform HTTP requests and deal with these with an await. The equivalent code for the first two examples becomes:

procedure TForm1.WebButton1Click(Sender: TObject);
var
  req: TJSXMLHttpRequest;
  jo, jv: TJSONOBject;
begin
  WebHttpRequest1.URL := 'https://www.tmssoftware.com/sample.json';
  try
     req := await(TJSXMLHttpRequest, WebHttpRequest1.Perform());
     jo := TJSONObject.Create;
     jv := jo.ParseJSONValue(string(req.response));
except // handle failure to execute request here end; end;
There is only one important note here!  As the form method WebButton1Click() has a promise handler, it needs to be marked with the async decorator or attribute!  This can be done for example with:
type
  TForm1 = class(TWebForm)
    [async]
    procedure WebButton1Click(Sender: TObject);
  end;
If you are like me, I think you will agree this is a more elegant way to write the code.

TMS WEB Core v1.7 includes many new TJSPromise functions

With the new upcoming v1.7 release, we overhauled the code already significantly, offering equivalent TJSPromise returning functions in many components and classes that were before based on class event handlers or anonymous methods for handling asynchronous behavior.  Don't worry, all existing event handlers and anonymous method handlers are still there for backwards compatibility!
Another class that was revised to offer promises was TWebForm. To show a new form, also here asynchronous behavior happens, as the form's HTML template needs to be loaded via a HTTP request. When a form was shown, an anonymous method construct was also introduced to mimic showing a form modally. Before TMS WEB Core v1.7, showing a new form modally could be done with the following code:
var
  newform: TForm2;

begin
  newform := TForm2.CreateNew(procedure(AForm: TObject)
    begin
      // anonymous method needed as the form is asynchronously loaded and initialized
      (AForm as TForm2).frm2Edit.Text := WebEdit1.Text;
    end
  );
  newform.Caption := 'Child form';

  // asynchronously handling closing the form via an anonymous method
  newform.ShowModal(procedure(AValue: TModalResult)
  begin
    ShowMessage('Form 2 closed with new value:"'+newform.frm2Edit.Text+'"');
    WebEdit1.Text := newform.frm2Edit.Text;
    newform.Free; 
  end
  );
end;
In TMS WEB Core v1.7, the TWebForm class got two TJSPromise functions, one to load the form and one to execute the form. With this approach, the code becomes way more readable:
procedure TForm1.WebButton1Click(Sender: TObject);
var
  newform: TForm2;
  mr: TModalResult;
begin
  newform := TForm2.Create(Self);
  newform.Caption := 'Child form';
  // load file HTML template + controls
  await(TForm2, newform.Load());

  // init control after loading
  newform.frm2Edit.Text := WebEdit1.Text;

  try
    // excute form and wait for close
    mr := await(TModalResult, newform.Execute);
    ShowMessage('Form 2 closed with new value:"'+newform.frm2Edit.Text+'"');
    WebEdit1.Text := newform.frm2Edit.Text;
  finally
    newform.Free;
  end;
end;

Just like we do in our components, embrace the new promises / await constructs in your code with the new TMS WEB Core v1.7 and it will make your code more elegant and cleaner!
Oh, and of course, the same applies to the upcoming TMS WEB Core for Visual Studio Code.

TMS WEB Core v1.7 beta will be shortly accessible for all TMS ALL-ACCESS users and later this month via an official release.
We look forward to your feedback & comments! Happy coding meanwhile.

Learn more about TMS WEB Core 1.7 Ancona?




Bruno Fierens




This blog post has not received any comments yet.



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