How to ensure the character is initialized before cahnges it's values (size, speed, etc.)

I load game data than set the size of the humanoid on server side. However it sometimes seems that I load and set them, than the default things load later and resets my values.

This is also true for my GUI, it has a default label with “???” text and I than send a remote event to update the text, but sometimes it remains “???” (I think it is set by my script than the GUI is fully loaded and loads the defauilt things)

My method:

Server code

game.Players.PlayerAdded:Connect(function(p)
	players[p.UserId] = p
	p.CharacterAdded:Connect(function(c)		
		humanoid = c:WaitForChild("Humanoid")			
		
		p.CharacterAppearanceLoaded:Connect(function(char)
			LoadStatsAndSetValues(p.UserId, humanoid)
		end)	 
	end)		
end)

function LoadStatsAndSetValues(userId, humanoid)					
 	local success, jsonStats = pcall(function()
		return statsStore:GetAsync(userId)
	end) 	

	stats  = HttpService:JSONDecode(jsonStats)	

  	humanoid.BodyWidthScale.Value = stats.size
	humanoid.BodyDepthScale.Value = stats.size
	humanoid.BodyHeightScale.Value = stats.size
	
        DeclareStatsEvent:FireClient(players[userId], stats.strength)
end

Client code:

DeclareStatsEvent.OnClientEvent:Connect(function(stats)
	strengthLbl.Text = stats.strength
end)

CharacterAppearanceLoaded is not neccessary in this case, and I would suggest just sticking to calling the LoadStatsAndSetValues function every time CharacterAdded is fired.

The client code should be strengthLbl.Text = stats Because you already send that value from the server.

I would also go against using GetAsync every time the character is added, and instead call GetAsync once and make sure that you store that value in a table (like how you store the player object in the players table). It could definitely be possible that the GetAsync calls may be failing and you don’t know about it, so I would also suggest using the success value and determining if the call worked as expected.

1,

Well I used to call LoadStatsAndSetValues without the p.CharacterAppearanceLoaded wrapper, I added it because I thought it solves the issue.

LoadStatsAndSetValues is already in CharacterAdded though, so I am not sure if I understand you.

2,

I would also go against using GetAsync every time the character is added, and instead call GetAsync once and make sure that you store that value in a table (like how you store the player object in the players table). It could definitely be possible that the GetAsync calls may be failing and you don’t know about it, so I would also suggest using the success value and determining if the call worked as expected.

I have to GetAsync everytime a new player joins, so should I call it in PlayerAdded? In that case, how do I ensure that the loaded data is available when the character is added? I am thinking about:

local allUserStats = {}

game.Players.PlayerAdded:Connect(function(p)
	players[p.UserId] = p
	p.CharacterAdded:Connect(function(c)		
		humanoid = c:WaitForChild("Humanoid")			

                allUserStats[p.UserId] = GetUserStatsAsync(p.UserId)

		spawn(function()
                    while allUserStats[p.UserId] = nil do
                          wait(.1)
                    end
                    LoadStatsAndSetValues(p.UserId, humanoid)
                end)
	end)		
end)

3,
What should I do if the GetAsync fails? I am thinking about recalling it until it succeeds. Like:

        local success = false
        
        while not success do
            success, jsonStats = pcall(function()
	        return statsStore:GetAsync(userId)
            end) 	
        end
        

bump. Anyone else has anything to add? It seems quite common to me.