TPromiseResult<T>
is a new type for web applications made with Delphi and TMS Web Core.
You can call await()
on any function that returns a TJSPromise and basically turn an asynchronous function call into a blocking function call.
When the promise gets resolved, await()
will return the expected type and life is good. But when a promise gets rejected, pas2js will translate this into an exception.
There is nothing wrong with exceptions, but you do need to catch them in a try..except
block or your web application will panic.
The bigger issue is that with web applications, everything is a JSValue
and this includes exception objects. Your exception object could be derived from Exception or from TJSError or anything else. We don't know. This makes error handling prone to... errors.
When you await for TPromise<T>.Execute() then this function will ALWAYS return a TPromiseResult<T>
You don't need a try..except
block and neither do you need a lot of boiler code to get the error message, potentially not getting the error message at all.
If the promise got resolved, TPromiseResult<T>.IsResolved is True and you can get the return value via TPromiseResult<T>.Value. But if the promise got rejected, TPromiseResult<T>.IsRejected is True and you can get the error message via TPromiseResult<T>.Error:
procedure TForm1.WebButton1Click(Sender: TObject);
var
PR: TPromiseResult<string>;
begin
PR := await(TPromiseResult<string>, TPromise<string>.Execute(@MyAsyncFunc));
if PR.IsResolved then
console.log('resolved: ' + PR.Value)
else
console.error('rejected: ' + PR.Error.Message);
end;
Then there are other methods such as TPromiseResult<T>.ifResolved and TPromiseResult<T>.ifRejected that allow for a functional programming style if you want. Here is an example:
procedure TForm1.WebButton1Click(Sender: TObject);
begin
await(TPromiseResult<string>, TPromise<string>.Execute(@MyAsyncFunc))
.ifResolved(procedure(value: string)
begin
console.log('resolved: ' + value)
end)
.&else(procedure(error: TPromiseError)
begin
console.error('rejected: ' + error.Message);
end);
end;
If you ever find yourself in a promise executor and you need to reject the promise, you can create an Exception but if you are using PromiseResult in your project, it is recommended to do this:
function MyAsyncFunc: TJSPromise;
begin
Result := TJSPromise.New(
procedure(resolve, reject: TJSPromiseResolver)
begin
reject(TPromiseError.Create('my error message'));
end
);
end;
Creating a class that derives from TPromiseError
allows for you to introduce your own custom errors. Here is an example:
type
TMyCustomError = class(TPromiseError)
strict private
FStatusCode: Integer;
public
constructor Create(const aMsg: string; const aStatusCode: Integer);
function Message: string; override;
end;
constructor TMyCustomError.Create(const aMsg: string; const aStatusCode: Integer);
begin
inherited Create(aMsg);
FStatusCode := aStatusCode;
end;
function TMyCustomError.Message: string;
begin
Result := Format('%d: %s', [FStatusCode, FMessage]);
end;
Now that you have defined your own custom error, here is how to reject a promise:
function MyAsyncFunc: TJSPromise;
begin
Result := TJSPromise.New(
procedure(resolve, reject: TJSPromiseResolver)
begin
reject(TMyCustomError.Create('my custom error message', 404));
end
);
end;