async problem

I've got a button that calls a method DoSomethingUsingService(...) that does this:

TmyWEBForm = class...
  . . .
  xyz : TXyz;

  [async]
  procedure asyncCallService( aLineNum : integer ); 

  procedure DoSomethingUsingService(const aText: string);
. . .
implementation
. . .
procedure asyncCallService( aLineNum : integer ); 
begin
  Log( '--- inside of asyncCallService' );
  . . .
  xyz := TXyx.Create;
  // fill up xyz
  Log( '--- xyz is ready' );
end;

procedure DoSomethingUsingService;
begin
  . . .
  try
    . . .
    Log( '+++ calling asyncCallService' );
    asyncCallService( n, abc ); // this fills an object we'll call xyz of type TXyz
    Log( '+++ returned from asyncCallService' );
    . . .
  finally
    if not Assigned(xyz) then
      LogError( '*** xyz is NIL' )
    else
    begin
      Log( '+++ field1='+xyz.field1 );
      Log( '+++ field2='+xyz.field2 );
      . . .
    end;
  end;
end;

It's my understanding that the call to asyncCallService(...) should be synchronous and block until it completes.

The problem I'm having is that it's not doing that. I've got Log statements inside of it that show this execution path:

+++ calling asyncCallService
+++ returned from asyncCallService
*** xyz is NIL
--- inside of asyncCallService
--- xyz is ready

it should be like this:

+++ calling asyncCallService
--- inside of asyncCallService
--- xyz is ready
+++ returned from asyncCallService
+++ field1=abc
+++ field2=whoosh

That is, the asyncCallService call is not blocking, but returning immediately.

Is this what it's supposed to do? If so, how do I get the behavior I want? The stuff after the call depends on the data returned by what asyncCallService gets back.

If not ... is it a bug? Or did I miss adding an attribute or something somewhere?

Try this.

  1. move [async] attribute to the DoSomethingUsingService in your interface.
  2. Instead of asyncCallService( n ) in the implementation of DoSomethingUsingService, try...
    await(asyncCallService( n ) )

The idea is that the method that is waiting needs to be marked as [async]. You'll get an error indicating as much if you don't set it that way. And the waiting is done with await().

hmmmm.... well, I'm not getting any errors, it just ran things out-of-order.

But, that solved the problem. :slight_smile:

However, now none of the debug statements that post to the Memo on the WebForm don't display anything. I thought that thread context was supposed to be the same as the main form? This code is in that same unit.

If I need to update the display, what's needed?

Hmmm. No threads here. How are you referencing the memo? Is JavaScript used anywhere, particularly event handlers? Sometimes it helps to fully qualify the name of the component in that case (Form1.Memo1 instead of just Memo1, even in the same form). Is there an error in the console when you try to do the update?

Nope, no errors. I just call Memo1.Lines.Add( 'blah blah' );

But the Good News is ... the overall code is now working and flowing in the proper order and direction. YAY! So no need for any more debug statements in this section. It's just curious that without the async properly working, I was able to post debug data to the form (in Memos). But once I got the async working, no debug data appears at all. It all runs as expected, but who knows where that data for the Memos it's going? No run-time errors either.

I'm almost ready to start incorporating some of that code we discussed a while back. It has been a long road getting to this point, but I've gotten everything that was working in the VCL-only version now migrated out and split between an XData service and a WEB Core front-end.

Actually, this approach would make for an excellent teaching example: build something as a single VCL app, then split it up into a back-end Service part and a WEB Core front-end. You start to see pretty quickly how mixing biz logic and UI stuff in the same procedures (often event handlers) is great way to drive yourself nuts. :slight_smile:

My first step was to isolate all of the biz logic into separate units to move over to the XData service. That made things a lot easier.

But I found along the way that there are three or four steps on each side of that "bridge" and if you change a parameter on a back-end service, you have to change it in all of those places on each side, and they're not always an exact mirror of each other. Also, because XData doesn't serialize / deserialize JSON data within WEB Core yet, that's an additional thing that needs to be addressed. And finally, there's the async stuff needed on the WEB Core side.

It's kinda like knowing you need to wear pants and socks and shoes everywhere, but the stuff you wear inside and what you wear outside is different, and it changes when you go through a doorway between inside and outside.

There's an opportunity for simplifying a lot of this with some code generators. Oh, how I wish Delphi allowed the use of separate compilation units that merge into a single class. (Actually, I wish the whole notion of compilation units aka "files" would just disappear and allow you to compose a "compilation unit" from database queries. There's no way we'll be able to make any significant headway at improving abstraction levels of code modules as long as everything that's related must be contained inside of the same "can", as if it's all meat from the same tuna fish.)

Plenty of obstacles... or opportunities!

I find with XData that I "pretend" that the services it provides are going to be consumed by strangers - people not using Delphi in particular, but other apps and other systems that I know nothing about. So when pushing data out or pulling data in, I try to use JSON that is as simplified as I can make it, wherever possible, and then convert it at either end (or both) if I need it to be something else. More work, perhaps, than passing something like a Delphi object, but it also forces the issue of making something that is a little more well-defined and accessible than it might otherwise be. It also means that I can pass whatever I like back and forth without having to do anything but refer to a JSON 'container' as the passing medium. Took a while, certainly, but JSON is growing on me :slight_smile:

Oh, and you'll likely find it is quicker/better/easier to log everything to the console, rather than to use Memos for debugging. Its actually one of the more useful aspects of JavaScript generally - a bit sad that such a thing isn't part of every Delphi app. Super useful. Also helps that it can be used to execute code directly. Might not be so easy for Delphi to do that, after all.

Wagner said that objects are not (de)serialized in WEB Core, so you need to do that manually, either converting to JSON, simple types, or arrays of same. That's what I did.

The challenge is that units that can be used by both sides either need to be carefully written so they don't use anything on one side that isn't in the other side, or you need a bunch of conditional compilations, or just make them two separate units. Hopefully pas2js and its RTL will catch up with Delphi fairly soon.

These are all very mechanical tasks that could easily be addressed by code generation tools.

I wish they'd put JSON import/export support into TObject so you don't have to deal with half a dozen different implementations.

How do you log to the console from the WEB Core side? I guess that's the Dev console in the browser you're referring to?

Yeah, that's what I mean about not assuming the REST API is going to be used by another Delphi application. Making it more generic kind of forces you to think about what needs to actually be exposed, versus the robotic "I have a class here that I want to use there" sort of approach. Not that its a better approach, just different.

As for logging - yes, just ...

console.log(whatever)

and it writes it out to the Dev console.

1 Like