- Регистрация
- 9 Май 2015
- Сообщения
- 1,486
- Баллы
- 155
Asynchronous programming is crucial for modern applications, but managing multiple callbacks and events can quickly turn into a tangled mess—commonly known as "callback hell". But the very need for asynchronous calls is to avoid GUI thread freezes when for example HTTP requests take an unpredictable amount of time. To tackle this challenge, we are excited to introduce beta support for promises in ! With promises, you can write clean, sequential code for cloud-based operations while maintaining all the benefits of asynchronous behavior.
The Challenge
relies on asynchronous requests to communicate with various cloud services, such as Google, PayPal, , and more. If you've worked with our components, you’re familiar with how asynchronous communication is handled via callbacks and events.
While effective, this approach can become difficult to manage. For example, to view an event from the Google Calendar API, you need to go through all these steps:
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 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 and . And Laurens also revitalized the (old) Delphi 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:
Example
Let’s break down a small example to understand the building blocks of using promises. In this example, we’ll request the bordering countries of a given country using the . The API returns an array of country codes for the bordering nations, so we’ll need to make additional requests to retrieve their full names.
1. Deferred Promises
The core mechanism relies on deferred promises, which are created using . This method takes an executor function as a parameter. The executor function provides two parameters, AResolve and AReject, which allow you to resolve or reject a promise after an asynchronous operation completes. These parameters can be persisted and invoked later or used in a callback directly:
//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 := '
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;
2. Chaining
ensures sequential execution of operations while preserving asynchronous behavior. Each .ThenBy call returns a new IPromise<T> and defines what happens after the current promise resolves. The value returned from each step is passed to the next in the chain, reducing the need for nested callbacks.
A simple example on chaining:
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;
In the snippet above, ExecuteRestCountryRequest is used to make an API call, and the response is parsed in a chained step to extract the desired data—in this case, the country name.
3. Chain multiple deferred promises
Understanding this step is important before moving on to the final step of waiting for multiple deferred promises to complete.
It might seem a bit complex at first glance, but combining multiple deferred promises with chaining is actually simple. The key to understanding this is that a promise is resolved when the Result is set. This means we need to return a promise from the .ThenBy method, ensuring that the chain continues only after the current promise has been resolved.
Here’s how it works in practice:
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);
4. Wait for multiple promises
allows us to wait for multiple promises to resolve, and we can apply the same logic as before. The key difference is that, instead of returning a single IPromise<T>, we now return an array of them:
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 := GetCountryNameFromCode(LBorders);
//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);
end;
Result := Void;
end)
.Main.Catch(procedure (E: Exception)
begin
//Show errors - if any:
ShowMessage(E.Message);
end);
end;
And that’s it! By calling GetBorderingCountries from a button click, you’ll fetch the list of neighboring countries while keeping the UI responsive.
Get Started and Share Your Feedback
If you'd like to see something more practical, particularly in combination with our TMS FNC Cloud Pack components, take a look at the TTMSFNCCloudGoogleCalendar demo we’ve prepared, available under the Demo\Promises folder! It not only demonstrates the concepts discussed above but also shows how to keep your codebase cleaner by inheriting from TTMSFNCCloudGoogleCalendar and handling promises internally.
While awaiting that Christmas dinner to bake in the oven, take a moment to explore how promises can simplify your workflows and make your code more maintainable. The implementation of promises is now available for registered users, integrated into TMS FNC Core as a BETA! To make the most of the Delphi-Promises library, be sure to , which offers detailed guidance to help you get started quickly.
The integration requires Delphi 10.2 or later, so ensure your development environment meets the minimum requirements.
Your feedback will play a valuable role in shaping the future of promises within our FNC range. Dive into the BETA, try it out, and let us know how promises are impacting your development experience!
The Challenge

relies on asynchronous requests to communicate with various cloud services, such as Google, PayPal, , and more. If you've worked with our components, you’re familiar with how asynchronous communication is handled via callbacks and events.
While effective, this approach can become difficult to manage. For example, to view an event from the Google Calendar API, you need to go through all these steps:
- 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 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 and . And Laurens also revitalized the (old) Delphi 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:
Example
Let’s break down a small example to understand the building blocks of using promises. In this example, we’ll request the bordering countries of a given country using the . The API returns an array of country codes for the bordering nations, so we’ll need to make additional requests to retrieve their full names.
1. Deferred Promises
The core mechanism relies on deferred promises, which are created using . This method takes an executor function as a parameter. The executor function provides two parameters, AResolve and AReject, which allow you to resolve or reject a promise after an asynchronous operation completes. These parameters can be persisted and invoked later or used in a callback directly:
//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 := '
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;
2. Chaining

A simple example on chaining:
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;
In the snippet above, ExecuteRestCountryRequest is used to make an API call, and the response is parsed in a chained step to extract the desired data—in this case, the country name.
3. Chain multiple deferred promises
Understanding this step is important before moving on to the final step of waiting for multiple deferred promises to complete.
It might seem a bit complex at first glance, but combining multiple deferred promises with chaining is actually simple. The key to understanding this is that a promise is resolved when the Result is set. This means we need to return a promise from the .ThenBy method, ensuring that the chain continues only after the current promise has been resolved.
Here’s how it works in practice:
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);
4. Wait for multiple promises
allows us to wait for multiple promises to resolve, and we can apply the same logic as before. The key difference is that, instead of returning a single IPromise<T>, we now return an array of them:
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 := GetCountryNameFromCode(LBorders);
//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);
end;
Result := Void;
end)
.Main.Catch(procedure (E: Exception)
begin
//Show errors - if any:
ShowMessage(E.Message);
end);
end;
And that’s it! By calling GetBorderingCountries from a button click, you’ll fetch the list of neighboring countries while keeping the UI responsive.
Get Started and Share Your Feedback
If you'd like to see something more practical, particularly in combination with our TMS FNC Cloud Pack components, take a look at the TTMSFNCCloudGoogleCalendar demo we’ve prepared, available under the Demo\Promises folder! It not only demonstrates the concepts discussed above but also shows how to keep your codebase cleaner by inheriting from TTMSFNCCloudGoogleCalendar and handling promises internally.
While awaiting that Christmas dinner to bake in the oven, take a moment to explore how promises can simplify your workflows and make your code more maintainable. The implementation of promises is now available for registered users, integrated into TMS FNC Core as a BETA! To make the most of the Delphi-Promises library, be sure to , which offers detailed guidance to help you get started quickly.
The integration requires Delphi 10.2 or later, so ensure your development environment meets the minimum requirements.
Your feedback will play a valuable role in shaping the future of promises within our FNC range. Dive into the BETA, try it out, and let us know how promises are impacting your development experience!