Blog
All Blog Posts | Next Post | Previous Post
Sequential Code for Async Cloud Requests with Delphi Promises!
Tuesday, December 17, 2024
The Challenge
- Authenticate the user.
- Retrieve a list of available calendars.
- Fetch the events for a specific calendar.
Promising Solution
Being so used to using promises in the context of web applications, where by design, several functions work asynchronously, we missed a promises implementation for a long time for native Delphi code. Especially in the area of using REST API functions, the ability to use promises significantly simplifies writing code that needs to be built with chained asynchronous calls.
It was with much enthusiasm that we discovered the promises library written by Laurens van Run from MendriX earlier this year. This triggered us to get in touch with Laurens and see how we could both spread the message about this fantastic new Delphi promises library and also leverage this in the context of our components, especially the TMS FNC Cloud Pack that is built on top of asynchronous REST APIs.
By working together with Laurens, we also contributed to make his promises library cross-platform and offer it integrated as part of TMS FNC Core to make using it from TMS FNC Cloud Pack as seamless as possible.
We thank Laurens so much for all his efforts that went into this. Laurens did a great job for the Delphi community and his promises library is not the only one. Laurens also actively contributes to projects like DelphiCodeCoverage and Delphi-Mocks. And Laurens also revitalized the (old) Delphi SonarQube plugin and worked with Embarcadero to bring attention to it.
Together with his colleagues at Mendrix and the Delphi community, Laurens drives forward innovation in the Delphi world. Check also what jobs Mendrix has to offer for Delphi developers here: https://werkenbijmendrix.nl/
Example
//We will query two endpoints:
//https://restcountries.com/v3.1/name/{CountryName}
//https://restcountries.com/v3.1/alpha/{CountryCode}
//Create a common request method that we can reuse
function TForm1.ExecuteRestCountryRequest(APath: string; ACountryOrCode: string): IPromise<string>;
begin
Result := Promise.New<string>(procedure (AResolve: TProc<string>; AReject: TProc<Exception>)
begin
c.Request.Clear;
c.Request.Host := 'https://restcountries.com/v3.1';
c.Request.Method := rmGET;
c.Request.Path := APath + '/' + ACountryOrCode;
c.Request.ResultType := rrtString;
c.ExecuteRequest(procedure (const ARequestResult: TTMSFNCCloudBaseRequestResult)
begin
if ARequestResult.Success then
AResolve(ARequestResult.ResultString)
else
AReject(Exception.Create('Request failed. No country or code found: ' + ACountryOrCode));
end);
end);
end;
function TForm1.GetCountryNameFromCode(ACode: string): IPromise<string>;
begin
Result := ExecuteRestCountryRequest('/alpha', ACode)
.ThenBy(function (const AValue: string): string
begin
//AValue contains the JSON response, parse it to get the name:
Result := GetCountryNameFromJSON(value);
end);
end;Promise.New<TVoid>(procedure(AResolve: TProc<TVoid>; AReject: TProc<Exception>) begin //Do the first promisified call end) .ThenBy(function(const AResult: TVoid): IPromise<TVoid> begin Result := Promise.New<TVoid>(procedure(AResolve: TProc<TVoid>; AReject: TProc<Exception>) begin //Do the second promisified call end); end) .ThenBy(function(const AResult: TVoid): IPromise<TVoid> begin Result := Promise.New<TVoid>(procedure(AResolve: TProc<TVoid>; AReject: TProc<Exception>) begin //Do the third promisified call end); end);
procedure TForm1.GetBorderingCountires(ACountry: string);
begin
//Start by getting the country name
ExecuteRestCountryRequest('/name', ACountry)
.Op.ThenBy<TArray<string>>(function (const AValue: string): IPromise<TArray<string>>
var
LBorders: TArray<string>;
LPromises: TArray<IPromise<string>>;
I, LBorderCount: Integer;
begin
//Parse the returned JSON to retrieve the list of
//bordering countries
LBorders := GetBorderingCountriesFromJSON(AValue);
LBorderCount := Length(LBorders);
SetLength(LPromises, LBorderCount);
//Create a promise for each country code, these are individual
//requests:
for I := 0 to LBorderCount - 1 do
LPromises[I] := GetCountryNameFromCode(LBorders[I]);
//Wait for all the promises to complete
Result := Promise.All<string>(LPromises);
end)
.Main.ThenBy<TVoid>(function (const AValues: TArray<string>): TVoid
var
I: Integer;
begin
//Cautiously update the UI reflecting the list:
if FFormNotDestroyed then
begin
for I := 0 to Length(AValues) - 1 do
Memo1.Lines.Add(AValues[I]);
end;
Result := Void;
end)
.Main.Catch(procedure (E: Exception)
begin
//Show errors - if any:
ShowMessage(E.Message);
end);
end;Get Started and Share Your Feedback
Tunde Keller
This blog post has received 5 comments.
2. Friday, December 20, 2024 at 10:39:29 AM
It seems their origial code from github relies on things that are not supported in D10.4, yet it says above that D10.2 or later should suffice.
wiz
3. Friday, December 20, 2024 at 11:15:56 AM
Sorry, this may have to do with WEB Core''s pas2js. Do you have a version of the Promises lib that works in WEB Core? (How''s it working in the Cloud lib with WEB Core?)
wiz
4. Friday, December 20, 2024 at 11:23:27 AM
Promises already exist in WEB Core for a long time, there was/is no need to bring this to WEB Core. It was brought to native apps here.
Bruno Fierens
5. Friday, December 20, 2024 at 11:23:49 AM
We adapted the code to make it work with 10.2 and newer
Bruno Fierens
All Blog Posts | Next Post | Previous Post
wiz