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");
}
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;