Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug, invalid
assignees: ''

---

Thanks for taking the time to fill out a bug report! Please make sure to be as specific as possible in your description and title.

**Issue description**
Describe your issue briefly. What doesn't work, and how do you expect it to work instead?

**Steps to reproduce**
Provide steps that can be used to reproduce the issue. Issues that are not reproducible are
unlikely to be resolved. If you include a minimal Delphi project below, you can detail what to look for here.

**Minimal Delphi code exhibiting the issue**
Please attach a ZIP file of a minimal Delphi project that exhibits the issue.
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug, invalid
assignees: ''
---
Thanks for taking the time to fill out a bug report! Please make sure to be as specific as possible in your description and title.
**Issue description**
Describe your issue briefly. What doesn't work, and how do you expect it to work instead?
**Steps to reproduce**
Provide steps that can be used to reproduce the issue. Issues that are not reproducible are
unlikely to be resolved. If you include a minimal Delphi project below, you can detail what to look for here.
**Minimal Delphi code exhibiting the issue**
Please attach a ZIP file of a minimal Delphi project that exhibits the issue.
32 changes: 16 additions & 16 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
---
name: Feature request
about: Submit a new feature request to help us improve
title: ''
labels: enhancement
assignees: ''

---

Thanks for your interest in this library! Please make sure to be as specific as possible in your description and title.

**Request description**
Describe your request briefly. What is missing, how would you solve it, and how do you expect it to work?

**Small code snippet**
Please provide a small piece of pseudocode to demonstrate the usage.
---
name: Feature request
about: Submit a new feature request to help us improve
title: ''
labels: enhancement
assignees: ''
---
Thanks for your interest in this library! Please make sure to be as specific as possible in your description and title.
**Request description**
Describe your request briefly. What is missing, how would you solve it, and how do you expect it to work?
**Small code snippet**
Please provide a small piece of pseudocode to demonstrate the usage.
86 changes: 86 additions & 0 deletions Core/Types/Next.Core.Promises.Cancellation.pas
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
unit Next.Core.Promises.Cancellation;

interface

uses
System.SysUtils, System.SyncObjs,
Next.Core.Promises.Exceptions;

type
/// <summary>
/// A read-only token that can be checked for cancellation.
/// Passed into promise chains and long-running operations for cooperative cancellation.
/// </summary>
ICancellationToken = interface
['{F7A3D8E1-2B4C-4D6F-9E1A-3C5B7D9F0A2E}']
/// <summary>
/// Returns True if cancellation has been requested.
/// </summary>
function IsCancelled: Boolean;
/// <summary>
/// Raises EOperationCancelled if cancellation has been requested.
/// Call this periodically inside long-running operations for cooperative cancellation.
/// </summary>
procedure ThrowIfCancelled;
end;

/// <summary>
/// A source that can trigger cancellation of an associated token.
/// Create a TCancellationTokenSource, pass its Token to promises, and call Cancel when needed.
/// </summary>
ICancellationTokenSource = interface
['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}']
/// <summary>
/// Returns the cancellation token associated with this source.
/// </summary>
function Token: ICancellationToken;
/// <summary>
/// Signals cancellation. All tokens derived from this source will report IsCancelled = True.
/// This operation is thread-safe and idempotent.
/// </summary>
procedure Cancel;
/// <summary>
/// Returns True if Cancel has been called.
/// </summary>
function IsCancelled: Boolean;
end;

/// <summary>
/// Implementation of ICancellationTokenSource and ICancellationToken.
/// Uses TInterlocked for thread-safe cancellation signalling.
/// </summary>
TCancellationTokenSource = class(TInterfacedObject, ICancellationTokenSource, ICancellationToken)
private
FCancelled: Integer; // 0 = not cancelled, 1 = cancelled
function IsCancelled: Boolean;
procedure ThrowIfCancelled;
function Token: ICancellationToken;
procedure Cancel;
end;

implementation

{ TCancellationTokenSource }

procedure TCancellationTokenSource.Cancel;
begin
TInterlocked.CompareExchange(FCancelled, 1, 0);
end;

function TCancellationTokenSource.IsCancelled: Boolean;
begin
Result := TInterlocked.CompareExchange(FCancelled, 0, 0) = 1;
end;

procedure TCancellationTokenSource.ThrowIfCancelled;
begin
if IsCancelled then
raise EOperationCancelled.Create;
end;

function TCancellationTokenSource.Token: ICancellationToken;
begin
Result := Self;
end;

end.
104 changes: 104 additions & 0 deletions Core/Types/Next.Core.Promises.Exceptions.pas
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
unit Next.Core.Promises.Exceptions;

interface

uses
System.SysUtils, System.Generics.Collections;

type
/// <summary>
/// Exception raised when an operation exceeds the specified timeout duration.
/// Used by IPromise&lt;T&gt;.Timeout to signal that a promise did not settle in time.
/// </summary>
ETimeoutException = class(Exception)
public
constructor Create; overload;
constructor Create(const AMessage: string); overload;
end;

/// <summary>
/// Exception raised when a cancelled operation is detected.
/// Used by the cancellation token system to signal cooperative cancellation.
/// </summary>
EOperationCancelled = class(Exception)
public
constructor Create; overload;
constructor Create(const AMessage: string); overload;
end;

/// <summary>
/// Exception that aggregates multiple exceptions into a single exception object.
/// Used by Promise.Any when all promises reject — contains all individual rejection exceptions.
/// The EAggregateException owns the exception objects it holds and frees them on destruction.
/// </summary>
EAggregateException = class(Exception)
private
FExceptions: TArray<Exception>;
public
constructor Create(const AExceptions: TArray<Exception>);
destructor Destroy; override;
/// <summary>
/// The array of inner exceptions. The EAggregateException owns these objects.
/// </summary>
property Exceptions: TArray<Exception> read FExceptions;
end;

implementation

{ ETimeoutException }

constructor ETimeoutException.Create;
begin
inherited Create('Promise timed out');
end;

constructor ETimeoutException.Create(const AMessage: string);
begin
inherited Create(AMessage);
end;

{ EOperationCancelled }

constructor EOperationCancelled.Create;
begin
inherited Create('Operation was cancelled');
end;

constructor EOperationCancelled.Create(const AMessage: string);
begin
inherited Create(AMessage);
end;

{ EAggregateException }

constructor EAggregateException.Create(const AExceptions: TArray<Exception>);
var
LMsg: string;
i: Integer;
begin
LMsg := 'All promises were rejected (';
for i := Low(AExceptions) to High(AExceptions) do
begin
if i > Low(AExceptions) then
LMsg := LMsg + ', ';
if Assigned(AExceptions[i]) then
LMsg := LMsg + AExceptions[i].Message
else
LMsg := LMsg + '<nil>';
end;
LMsg := LMsg + ')';

inherited Create(LMsg);
FExceptions := AExceptions;
end;

destructor EAggregateException.Destroy;
var
i: Integer;
begin
for i := Low(FExceptions) to High(FExceptions) do
FreeAndNil(FExceptions[i]);
inherited;
end;

end.
Loading