What's the Difference?

Hi, so I have a question about this system of DataStores,
I noticed some people create a Variable before the pcall() to get Data from the pcall(), But you can also make it so you can get Data from the pcall() with the Second Variable

local Success, Data = pcall(function()
	return DataStore:GetAsync(self.Key)
end)

What would be so Different about it? Everybody says that the Version above I have is wrong and I should do it the other way. This is the other way in question:

local Data
local Success, Err = pcall(function()
	Data = DataStore:GetAsync(self.Key)
end)

I’m a bit confused as both do the exact same thing, besides the err Variable, However it is almost never used unless GetAsync or SetAsync Fails.

What’s the Difference?
Are they the same thing?

They are semantically the same, only difference is how both are used differently. One might not be easy to separate whether it returns an error or actual data. Although success is what is most important, you might need that to understand what is causing problems. The latter is probably easier for error handling, separating the data from the error message that it could return.

1 Like

Hi, Sorry for the Late Response

I understand that, but people are telling me its completely incorrect.

No, it is completely correct.
As @Operatik stated, the latter scope is better for error handling.
Let’s clear things up:

local success, data = pcall(function()
    return DataStoreService:GetDataStore("Cat")
end)
    
print(success, data)
-- Outputs: true Cat (only assuming DataStore is working LOL)

Within this scope, the function returns two arguments: success, the boolean that indicates if the pcall function finished execution without errors, and data, any content we requested for that the function returns.
A pcall will always return a boolean as its first argument, which indicates if the block finished executing without errors. If the scope errors, this will be false, and the same pcall's second argument is the error message.

If it were to return an error, for example:

local success, data = pcall(function()
    return DataStoreService:GetDataStore("")
end)
    
print(success, data)
-- Outputs: false DataStore name can't be empty string

Data is instead the appropriate error message, which is the second argument returned from the pcall.

This works the same for the other scope:

local data
local success, err = pcall(function()
     data = DataStoreService:GetDataStore("Cat")
end)
print(success, data, err)
-- Outputs: true Cat nil

The pcall only returns the boolean as there is no errors, or anything that the scope returns. Then, data is set as a variable, which is returned from the :GetDataStore() method.

local data
local success, err = pcall(function()
    data = DataStoreService:GetDataStore("")
end)
print(success, data, err)
-- Outputs: false nil DataStore name can't be empty string

data is nil because the request errors, and the method did not return anything or nil. The pcall returns two arguments, the boolean, and the error.

Essentially, it does not matter whether you use one or the other. They are both equal, except the latter is better to separate the error and the data.

Also for the people telling you it is incorrect, tell them they are the ones who are not being correct.

1 Like

The Success variable indicates whether the datastore is working and has been successfully accessed. If this is nil then access to the DataStore has failed, either because Roblox servers are down or that single attempt failed to make connection, it’s often prudent to wait a little time (like 1 second) and make another attempt and maybe repeat this 3-4 times. Under these conditions the variable ‘Err’ will now contain some information about the failure. If the Success variable returns true (i.e. not nil) then the pcall() successfully accessed the DataStore, now the Err variable will either be nil (if no data exists on this key for this player) or will contain the players data for this key. Either way, the Success variable is paramount in understanding the return conditions of the pcall().

It is better to have a SessionData that is used to store and change the players data, this allows failed DataStore access’ to not scrub a valid players data, i.e.

local DataStoreService = game:GetService("DataStoreService");

local playerDataStore = nil;
local saveInStudio = false;

--=================================================================================
-- Local copy of the table containing player data
--=================================================================================
local sessionData = {};
--=================================================================================
-- When players fail to load data, their key is put in a list so that
-- their data does not save and override existing data
--=================================================================================
local tempData = {};

-- your blank player data structure/table
local NEW_PLAYER_DATA = {};

if (not saveInStudio) and game.GameId ~= 0 then
	playerDataStore = DataStoreService:GetDataStore("PlayerData");
else
	warn("Warning: Data will not be saved. Please publish place and/or enable saveInStudio flag.");
end

-- load a players data from the DataStore
local function loadData(player)

	local userId = player.UserId;
	local key = "player_" .. tostring(userId);

	-- Return data from the player cache if already loaded and is not flagged as temporary
	if sessionData[key] and not tempData[key] then
		return sessionData[key];
	end	
	
	-- Try to load data from the DataStore
	if playerDataStore then

		-- Player data does not exist in sessionData so is not currently loaded, let's go get it
		local success, data = pcall(function()
			return playerDataStore:GetAsync(self.Key);
		end)

		-- DataStore access was successful
		if success then

			if data then
				-- DataStore is working, and data exists for this player so assign this UserId key to the session data
				sessionData[key] = data;
			else
				-- DataStore is working, but no current data for this player
				sessionData[key] = NEW_PLAYER_DATA;
			end
	
			-- remove tempData[key] if it exists (i.e. this is the second or third attempt at calling this function)
			if tempData[key] then
				tempData[key] = nil;
			end

			-- DataStore is working and access was successful so return true
			return true;

		end

		-- DataStore access has failed
		warn( "Cannot access data store for ", key );

	end

	-- Could not load data, treat as new player
	sessionData[key] = NEW_PLAYER_DATA;

	-- When players fails to load data, their key is put in a list so that their
	-- data does not save and override existing data
	tempData[key] = true;

	-- data store failed so return false
	return false;

end

-- save a players data checking for the temporary sessionData first
local function saveData(player)

	local userId = player.UserId;
	local key = "player_" .. tostring(userId);

	-- Save data if it exists and is not temporary (i.e. do not scrub a players real DataStore with this temporary assigned data)
	if sessionData[key] and not tempData[key] then
		
		-- now save the players data to the DataStore.
		local tries = 0;
		local success = nil;
		repeat
			
			tries = tries + 1;
			success = pcall(function()
				playerDataStore:SetAsync(self.Key,sessionData[key]);
			end)
			
			if not success then wait(2) end;
			
		until tries == 3 or success
		
		if not success then
			warn( "Cannot save data for ", key );
		else
			warn( "Saved data for ", key );
		end
			
		return success;
		
	end

end

As I said you can perform repeat attempts on the loadData function in-case the failed return was a single DataStore access fault and not due to the DataStore being offline during server outages.

They do the same thing. I would usually end up writing something like this:

local success, result = pcall(function()
    return DataStore:GetAsync(self.Key);
end)

if success then
    -- data retrieved successfully, you can access it through the result variable
else
    --data retrieval failed, print error or do something else
    print("GetAsync() failed. Error:", result);
end

Both pieces of code do the same thing. If you wanted to be pedantic about it, you could argue that the second excerpt will create the Data variable unnecessarily however it makes the code more readable. It’s up to you really. Do what makes the most sense to you! :^)

1 Like

Oh yeah, I almost forgot. the conventional naming of the returns are success, result. This is pretty much similar to how JavaScript deals with asynchronous operations.

You can actually simplify this code and make it more ‘proper’.

local Success, Data = pcall(DataStore.GetAsync, DataStore, self.Key)

This is wholly equivalent to the first example, but it doesn’t create the unneeded anonymous function in the pcall.

1 Like

This is basically what I’m already doing.

if Success then -- pcall was successful
    if Data then
        self.Data = Data -- Loaded Data (Base Data is preloaded, this is just applying a new table of the Data)
    else -- No Data
        -- warns about No Data
    end
else -- pcall fails
    -- kicks Player
end

This is basically a rundown of stuff I already understand.

Thanks.

I see a lot of Problems with your code, I’m going to go over some of them.

tostring() is useless here, Lua will Automatically Convert this into a string

Plus, the only time adding spaces to concatenations is useful is with the Numbers themselves:

print(10..23) -- Lua will think its a decimal

print(10 .. 23) --  Lua finds a concatenation, prints: "1023"

This would be an Incorrect use of self, nowhere I see any specification of self, nor is it a ModuleScript function, functions with a colon : makes it so self is already specified while modules with a period . doesnt have this, you would need to create it yourself.

Again, Will automatically be converted by Lua

No self specified.

That’s what I have, I store the Data on the Server as the DataStore stuff is within a ModuleScript, which is why I’m using self

The self variable comes from the code in your original post. I saw no need to change it because I made the assumption that your code came from a ModuleScript. The rest of your comments are neither here nor there.

I always use tostring() whether under-the-hood language features claim to do it for you or not, and under no circumstance will "player_" .. tostring(UserId) produce anything close to the examples you indicated below since you are concatenating two integers and not a string and an integer.

print(10…23) – Lua will think its a decimal
print(10 … 23) – Lua finds a concatenation, prints: “1023”

This is basically the ModuleScript, Using a regular function doesnt specifiy self, it never was like that. Plus, Its getting a Little off Topic, so anyway:

local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("A")

------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------

local PlayerData = {}
PlayerData.__index = PlayerData

function PlayerData.new(Player: Player)
	local self = setmetatable({}, PlayerData)
	
	self.Player = Player
	self.Key = "profile/data/"..Player.UserId
	self.Data = {} -- There is no Data yet
	
	return self
end

function PlayerData:Load()
	local Success, Data = pcall(function()
		return DataStore:GetAsync(self.Key)
	end)
	
	if Success then
		if Data then
			self.Data = Data
		else
			warn("No Data found for", self.Player)
		end
	else
		self.Player:Kick("Failed to Access your Data")
	end
end

-- for the :Save, I would Basically do the Same thing as the :Load

return PlayerData

You didn’t read what I said.

However, idk if that’s to do with --!strict

So yeah, I’ll give you the solution as everybody is basically saying the same thing as you.
(maybe the Exception of @JarodOfOrbiter , but thats basically all)

Sorry if I notify people a lot, its just something I do.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.