Hi,
While building a Reactive Web App I got some issues and some unexpected behavior while debugging it.
Built a test screen with a block inside and assigned all events to them with the following code:
JavaScript1:
console.log('Screen1:OnInitialize1');
JavaScript2:
setTimeout(function(){ $resolve(); }, 5000);
JavaScript3:
console.log('Screen1:OnInitialize2');
And got the following output on the console:
Screen1:OnInitialize1 Screen1:OnInitialize2 Block1:OnInitialize1 Block1:OnReady1 Screen1:OnReady1 Block1:OnInitialize2 Block1:OnReady2 Block1:OnRender1 Screen1:OnReady2 Screen1:OnRender1 Block1:OnRender2 Screen1:OnRender2
So, the issue is that the block Block1:OnReady runs while the Block1:OnInitialize is not yet fulfilled. I would expect something like this:
await OnInitialize(); await OnReady(); await OnRender();
But clearly that's not what is happening, at least on the blocks. On the screens seems to be correct.
By the way, the same also applies on the OnApplicationReady event. Copying the same code into there the Screen1:OnInitialize will run before the OnApplicationReady is fulfilled.
I don't expect at all for this to be the expected behavior so, please let me know if I'm wrong.
Hi Luis,
setTimeout(function(){...}, 0) simply queues the code to run once the current call stack is finished executing.
it's a asynchronous call and will not execute concurrently.
Hope this help!!!.
Cheers,
Ali Amin
Hi Ali Amin,
Yes, I'm fully aware of that, but when you have '$resolve();' inside of a Javascript block it will wait till it's called, in other works, until the promise is fulfilled, more info here.
Regarding the issue, in general terms we have this for the screen:
Screen1:OnInitialize1 Screen1:OnInitialize2 Screen1:OnReady1 Screen1:OnReady2 Screen1:OnRender1 Screen1:OnRender2
And this for the Block:
Block1:OnInitialize1 Block1:OnReady1 Block1:OnInitialize2 Block1:OnReady2 Block1:OnRender1 Block1:OnRender2
The code is exactly the same but on the block level the OnReady is fired before the OnInitialize is completed/fulfilled (Block1:OnInitialize2).
So, on the screen level the OnReady will only be fired once the OnInitialize is completed/fulfilled. This means that it will wait for the promises ($resolve() calls) on the Javascript blocks.
On the Block level, the OnReady event won't wait until the OnInitialize is completed/fulfilled (if only have sync code there of course it will).
I went over the documentation here and found this OnInitialize info:
In Blocks, including any server action calls or local storage operations in this flow might cause the render of the Block to start before this action finishes. You will have issues if you set any variables in this flow that affect the render.
It is not very clear, but in the end I think it's related with the issue reported. Not sure if it was intended to be like that or it's just how it was implemented... but I would expected the behavior on screen & blocks to be aligned and, at the moment, it's not.
I have just noticed the same behavior and it was very surprising to me. In my case, there is nothing uncommon happening in the handlers, except the local data access (which might be the problem). The reason why I noticed this was because the variable used in OnReady was set in OnInitialize but in OnReady it was empty. I had to debug to see it, and after finding this topic, I can confirm that OnInitialize starts before OnReady, but completes after it. This can not be intended, because it breaks the basic assumption that you can use variables assigned in OnInitialize.
OnInit should not have any "slow" action, but OnReady to be executed definitely after OnInit finished!!!! Otherwise soon or later that could be a huge headache for any case.
The example with 3 JS blocks and $resolve() action works as it should from my point of view. The third block is executed after first and second blocks but asynchronously. So in spite of execution all 3 JS blocks inside the same client Outsystems action some part of the code are executed synchronously. Since there is action with promise the some part of code is placed to callback and executed after promise is resolve. So OnInitialize is completed after executing second block and nothing blocks to run OnReady action since OnInitialize action returned control.I modified the second block as:console.log('Screen1:OnInitialize1.1');setTimeout(function(){ console.log('Screen1:OnInitialize1.2'); $resolve(); }, 5000);console.log('Screen1:OnInitialize1.3');and output is following:Screen1:OnInitialize1Screen1:OnInitialize1.1Screen1:OnInitialize1.3Screen1:OnInitialize1.2Screen1:OnInitialize2The 1.3 log before 1.2 means that setTimeout(function(){ console.log('Screen1:OnInitialize1.2'); $resolve(); }, 5000); doesn't block next line of the code until promise is resolved while OnInitialize2 after OnInitialize1.2 means that third block is executed after timeout but OnInitialize1.2 is executed in timer callback and OnInitialize2 in the promise callback.Outsystems places all JS blocks to different functions and run them according to JS execution flow. The second JS block has Promise definition and timer with callback:
define("Valery.MainFlow.Test.mvc$controller.OnInitialize.JavaScript3JS", [], function () {
return function ($actions, $roles, $public) {
return new Promise(function ($resolve, $reject) {
console.log('Screen1:OnInitialize1.1');
setTimeout(function(){ console.log('Screen1:OnInitialize1.2'); $resolve(); }, 5000);
console.log('Screen1:OnInitialize1.3');
});
};
define("Valery.MainFlow.Test.mvc$controller.OnInitialize.JavaScript2JS", [], function () {
define("Valery.MainFlow.Test.mvc$controller.OnInitialize.JavaScript1JS", [], function () {
});As result first timer callback is executed, next timer function resolve promise and promise callback is executed. What is inside promise callback? We can see that content of promise callback is the rest part of the client action namely third JS block.In case of using local storage aggregate OS introduces promise as well. So all code after aggregate is executed in the callback asynchronously. It means that reading local storage in OnInitialize action is not reliable way to use result somewhere except UI because we don't know when result will be available. So only Fetch data action is reliable way to get data from local storage. In such case you have OnAfterFetch callback to process result just after it is ready in the promise callback.