How to perform a synchronous HTTP request and get the result in TMS WEB Core

Hello,

I am working on a TMS WEB Core project and I need to perform an HTTP request to an API, get the response, and return it directly from a function. Essentially, I want to do something like:

function GetApiResult(URL: string): string;
begin
  // make HTTP request
  // return the response text
end;

I have tried using TWebHttpRequest.Perform, TJSPromise, and async callbacks, but nothing seems to allow me to get the result synchronously.

Is there a way in TMS WEB Core to make a synchronous HTTP request that can return the response directly, or a correct pattern to achieve the same effect?

Thank you in advance for your guidance.

Best regards

Use await() or TAwait to make these asynchronous calls sequential.
It is all explained here:

Here is my sample code:

Unit1.pas

uses JS;

TMyObject1 = class(TObject)  
  [async]  
  function ThisMethodIsAsync: Boolean;  
end; 

function ThisMethodIsAsync: Boolean; 
begin
    Result := true;
end;



Unit2.pas

TMyObject2 = class(TObject)  
  procedure CallAsyncFunction;  
end; 

procedure CallAsyncFunction;
var LRes: Boolean;
begin
    LRes := await(TMyObject1.ThisMethodIsAsync());
end;

with this code I get the error

[Error] Unit2.pas: await only available in async procedure

I tried to remove the "await" and I got

[Error] ULogin.pas(97): Incompatible types: got "TJSPromise" expected "Boolean"

What am I missing?

The await attribute needs to be set on the procedure from where the await will be executed, i.e. on CallAsyncFunction

I'm not sure I understood how to solve the issue. Could you send me a sample code?

In the guide I found this:

procedure TMyObject.CallAsyncFunction;
var
  lBuffer: Integer;
begin
  lBuffer := await(ThisMethodIsAsync);
  ...
end;

and that is exactly what I did.

Do I have to do this instead?

Unit2.pas

TMyObject2 = class(TObject)  
    [async]
    procedure CallAsyncFunction;  
end; 

procedure CallAsyncFunction;
var LRes: Boolean;
begin
    LRes := await(TMyObject1.ThisMethodIsAsync());
end;

Kind regards

Yes, those methods where await() is used need to be decorated with the async attribute

I applied the suggested changes, but now I’m getting the error

Incompatible types: got "TJSPromise" expected "Boolean"

Use:

LRes := await(boolean, TMyObject1.ThisMethodIsAsync());

Updated code:

Unit1.pas

uses JS;

TMyObject1 = class(TObject)  
  [async]  
  function ThisMethodIsAsync: Boolean;  
end; 

function ThisMethodIsAsync: Boolean; 
var FWebHttpRequest : TWebHttpRequest;
    LRes: String;
begin
   FWebHttpRequest := TWebHttpRequest.Create(nil);
   FWebHttpRequest.URL := 'https://api.example.com/data';
   FWebHttpRequest.Headers.AddPair('Content-Type', 'text'); 
   LRes := await(string, FWebHttpRequest.Perform);
   Result := LRes <> '';
end;



Unit2.pas

TMyObject2 = class(TObject)  
  procedure CallAsyncFunction;  
end; 

procedure CallAsyncFunction;
var LRes1: Boolean;
begin
    LRes1 := (TMyObject1.ThisMethodIsAsync());
end;

As you can see, CallAsyncFunction is not async anymore.

Now I'm getting this error:

[Error] Unit2.pas: Incompatible types: got "TJSPromise" expected "Boolean"

Which makes no sense at all.

Why do you write

procedure CallAsyncFunction;

while it should be:

procedure TMyObject2 .CallAsyncFunction;

...?

Sorry, I forgot the "TMyObject2" in my sample code

I checked this here with the team and when a function is decorated with the async attribute, it is implicitly treated as a function returning a TJSPromise.
To catch the result, an await() is needed. This is due to how promises are handled in the underlying JavaScript code this Pascal code is transpiled to.

So CallAsyncFunction has to be async as well?

Yes, since it is still a method where internally an await() is happening.

So how can I make this work?
I don't want to make CallAsyncFunction async and I'm not sure how this works.

If the caller needs the resolved result, then it will need to perform an await().

The whole technical background about what is happening in the browser in JavaScript code is described here

1. Inside a function with await

If you write a function that uses await, that function must be declared as async.
Example:

async function fetchData() {
  const result = await fetch("https://api.example.com/data");
  return result.json();
}

2. Calling that function

When you call fetchData(), it returns a Promise, because every async function in JavaScript always returns a Promise (even if you explicitly return a value, it’s wrapped in a Promise).

Example:

function caller() {
  const data = fetchData(); // data is a Promise
  console.log(data);
}

Here, data is a Promise, not the actual JSON result. If you need the resolved value, you must use await or .then().

3. Do you need to await?

  • Yes, if the caller function needs the resolved value:
async function caller() {
  const data = await fetchData();
  console.log(data); // real result
}
  • No, if you just want to kick off the async task and don’t care about when it finishes:
function caller() {
  fetchData(); // returns a Promise, but we ignore it
  console.log("I didn't wait");
}

:white_check_mark: Rule of thumb:
If you need the result, await it (and make the caller async).
If you only care about triggering the async work, you don’t need to await.

Thank you very much. Is there a way to make this in delphi?

Well, that IS available in Delphi and what I tried to explain in the first place in this thread.

I made a few changes but nothing works for me.
Here is my sample code:

Unit1.pas

TMyObject1 = class(TObject)  
private
    FResponse: Boolean;
public  
  function FirstFunction(): boolean;
  function ReturnMethonAsyncResult() :boolean; 

  [async]  
  function ThisMethodIsAsync(): Boolean;
end;

function TMyObject1.ThisMethodIsAsync(): Boolean;
var Req: TWebHttpRequest;
begin
    Req := TWebHttpRequest.Create(nil);
   // code for Req  
   Result := await(boolean, Req.Perform) // I tried also "Req.Execute"
   FResponse := Result;
end; 

function TMyObject1.ReturnMethonAsyncResult() :boolean;
begin
      // Result := ThisMethodIsAsync(); error: Incompatible types: got "TJSPromise" expected "Boolean" 
     ThisMethodIsAsync();
     Result := FResponse;
end; 

function TMyObject1.FirstFunction(): Boolean;
begin
    Result := ReturnMethonAsyncResult();
   // the result will be always false
end; 
end; 

I also tried to make ReturnMethonAsyncResult async but I get the error:

function TMyObject1.ReturnMethonAsyncResult(): Boolean;
begin
 Result := await(boolean, ThisMethodIsAsync);
end;

function TMyObject1.FirstFunction(): Boolean;
begin
    Result := ReturnMethonAsyncResult();
   // Incompatible types: got "TJSPromise" expected "Boolean"
end;

I can't understand what am I missing. It seem that await is not working properly.

Please accept this as my final answer. I repeated the same now several times.

You CANNOT hide the fact that a promise is returned by an asynchronous call, even when you wrap that asynchronous call into another seemingly synchronous call. If there is a promise returned in the chain, this promise needs to be awaited for. This is also what the lengthy response based on underlying JavaScript explains.

In this example, an asynchronous HTTP request is wrapped into a function and when calling that function, it still needs to be awaited for:

type
  TForm3 = class(TWebForm)
    WebHttpRequest1: TWebHttpRequest;
    WebButton1: TWebButton;
    WebMemo1: TWebMemo;
    [async]
    procedure WebButton1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    [async]
    function DoDownload: string;
  end;

implementation

function TForm3.DoDownload: string;
var
  res: TJSXMLHttpRequest;
begin
  webhttprequest1.URL := 'https://download.tmssoftware.com/tmsweb/fishfacti.json';
  res := await(TJSXMLHttpRequest, WebHttpRequest1.Perform);
  Result := string(res.response);
end;

procedure TForm3.WebButton1Click(Sender: TObject);
begin
  webmemo1.Lines.Text := await(string, DoDownload);
end;