Reputation: 317
I'm developing a Wrapper for the Shopee API as a Delphi package, it's open source and available on my gh profile. I already ran into a problem similar to this one which was answered here. But now I can't see the way around anymore.
It starts here:
constructor TShopeeContext.Create(AMode, AAPIKey, APartnerID: string);
begin
if (AMode <> TEST_SHOPEE) and (AMode <> PRODUCTION_SHOPEE_BR) then
raise Exception.Create('Mode Invalid for Context');
FHost := AMode;
FAPI_Key := AAPIKey;
FPartnerID := APartnerID;
UpdateInternalConfigFields;
if FRefreshToken = '' then
FHasRefreshToken := False
else
FHasRefreshToken := True;
// Checks if the authorization has been granted and is younger than 365 days.
if (not FAuthorizationGranted) or ((Now - FAuthorizationDate) > 365) then
begin
Authorize;
end;
end;
After the Authorize;
call, any instruction dependent on Authorization Code falls into Undefined Behavior because there's no guarantee I have a code. I would need to wait for Authorize to return before following on. My issue arises from the fact that Authorize creates an object and starts a routine that is Asynchronous, a HTTP listener:
procedure TShopeeContext.Authorize;
var
Authorizator: TShopeeAuthorizator;
begin
// Request Authorization;
Authorizator := TShopeeAuthorizator.Create(FHost, FAPI_Key, FPartnerID, 8342);
try
Authorizator.AuthorizationRequest;
finally
Authorizator.Free;
end;
end;
The AuthorizationRequest
procedure is implemented as:
procedure TShopeeAuthorizator.AuthorizationRequest;
begin
// Obtem a assinatura da chamada
FSignature := GetPublicSignString(API_PATH_AUTHORIZATION, FTimestamp);
// Constroi os parametros
FRequestParameters.AddItem('partner_id', FPartnerID);
FRequestParameters.AddItem('timestamp', FTimeStamp);
FRequestParameters.AddItem('sign', FSignature);
FRequestParameters.AddItem('redirect', REDIRECT_URL_WS+':'+IntToStr(FPort));
ShellExecute(0, 'open', PChar(GenerateRequestURL(API_PATH_AUTHORIZATION, FRequestParameters)), nil, nil, SW_SHOWNORMAL);
FCatcherServer := TCatcherServer.Create();
FCatcherServer.OnFieldsReady := FieldsReadyHandler;
FCatcherServer.Listen(FPort);
end;
and FieldsReadyHandler
is the subject of my older question, and the current version is:
procedure TShopeeAuthorizator.FieldsReadyHandler(Sender: TObject; Code,
AuthorizatedID: string);
begin
// Handle Code, Auth Type and AuthorizatedID.
FConfigurator := TConfiguratorFile.Create;
try
FConfigurator.SaveAuthorizationInfo(Code, AuthorizatedID, (Sender as TCatcherServer).AuthorizationType);
FSuccess := True;
finally
FConfigurator.Free;
end;
TThread.Queue(nil, procedure
begin
Sender.Free;
end);
end;
Usually it's a matter of HOW to do that, but honestly at this point I'm thinking that I overcomplicated it and don't even know WHAT I should do to overcome this. Anyways, if I were to rewrite I can see my self falling in the same spot where my subsequent tasks depends on the async task to finish. I also tried to add an event to inform the Context that it already obtained the code but (my shot is) it raises access violation because of the way the object is released.
Sorry I can't synthesize a TL;DR for this one, but if I had to index my questions the output would be: What is the best approach to this type of problem and how is it handled in pascal? In JS, for example, I would chain infinite .then()
until the routine doesn't need the async part anymore. I also feel it is going to become a frequent pain if I don't learn to handle it now as requests are asynchronous.
Thanks in advance.
Upvotes: 2
Views: 421
Reputation: 36664
Reference: Waiting for a Task to Be Completed
To do this, use an event object. Event objects (System.SyncObjs.TEvent) should be created with global scope so that they can act like signals that are visible to all threads. When a thread completes an operation that other threads depend on, it calls TEvent.SetEvent. SetEvent turns on the signal, so any other thread that checks will know that the operation has completed.
When your authorization is done, set the event:
procedure TShopeeAuthorizator.FieldsReadyHandler(Sender: TObject; Code,
AuthorizatedID: string);
begin
// Handle Code, Auth Type and AuthorizatedID.
FConfigurator := TConfiguratorFile.Create;
try
FConfigurator.SaveAuthorizationInfo(Code, AuthorizatedID, (Sender as TCatcherServer).AuthorizationType);
FSuccess := True;
// Notify
Event1.SetEvent;
finally
FConfigurator.Free;
end;
TThread.Queue(nil, procedure
begin
Sender.Free;
end);
end;
And in the TShopeeContext, use the Event to wait until authorization completed, or a given timeout period elapsed:
if (not FAuthorizationGranted) or ((Now - FAuthorizationDate) > 365) then
begin
Event1.ResetEvent; { clear the event before }
Authorize;
if Event1.WaitFor(20000) <> wrSignaled then
raise Exception;
end;
Notes:
Upvotes: 3