UpdateAsync() DataStore

Hello, after I learnt why UpdateAsync() is safer than SetAsync(). So I tried to make a code using UpdateAsync(), and I need some reviews on if I did my code right, and any optimizations for the code. Thanks in advance.

local DSS = game:GetService(“DataStoreService”)
local CashDS = DSS:GetDataStore(“CashDataStore”)

local function UpdateData(oldVal)
    local previous = oldVal.Cash or {oldVal.Cash = 0)
    if oldVal.Cash == previous then
        oldVal.Cash += 20
    else return nil end
end

game.Players.PlayerAdded:Connect(function(plr)
    local key = plr.UserId

    local folder = Instance.new(“Folder”)
    folder.Name = “leaderstats”
    folder.Parent = plr

    local cash = Instance.new(“IntValue”)
    cash.Name = “Cash”
    cash.Parent = folder

    local DataTable

    local success, failure = pcall(function()
        DataTable = CashDS:GetAsync(key)
    end)

    if success then
        cash.Value = DataTable.Cash or 0
    else return error(“Failed to get DataStore. Error code: ”..failure) end
end)

game.Players.PlayerRemoving:Connect(function(plr)
    local key = plr.UserId

    local DataTable = {
        Cash = plr.leaderstats.Cash.Value
    }

    local success, failure = pcall(function()
        CashDS:UpdateAsync(key,UpdateData(DataTable))
    
    end)

    if success then
        print(“Successfully saves player’s data.”)
    else return error(“Failed to save data. Error code: ”..failure) end

— code is untested yet since I’m on mobile.
2 Likes

This line won’t work because then you’d be calling UpdateAsync with the second parameter as a non-function value (this would evaluate to use the return value of UpdateData for the TransformFunction). You only need to be passing UpdateData here. Remember that your functions are variables.

CashDS:UpdateAsync(key, UpdateData)

Looks fine otherwise, I guess. There’s just simple recommendations that can be made.

  • Assign the anonymous function being connected to PlayerAdded to a variable. Connect it later and run it for existing players. If your thread yields or doesn’t run in time, players in the server before this code runs will not get data.

  • Parent the Folder to the player after you finish working with DataStores. Your current order is [1], you should instead make it [2]. List is below.

  • pcall is a callback, return the value instead of using another variable. Example is below. Doesn’t really make a difference but better to use pcall as it’s intended to be used when it comes to DataStores - this variable pattern is really weird.

DataStore Flow:

Current: Create folder → Parent folder → Create cash → Parent cash → Get data → Set cash
Change to: Create folder → Create cash → Get data → Set cash → Parent cash → Parent folder
Why: You’re just modifying pieces of memory. Always parent after you make your changes.

pcall use:

-- Current
local DataTable

local success, failure = pcall(function ()
    DataTable = CashDS:GetAsync(key)
end)

-- Change to
local success, result = pcall(function ()
    return CashDS:GetAsync(key)
end) -- *

* result will either be what GetAsync returns or a string representing a caught exception depending on what success returns. In case of DataStore outage where pcall success is true but GetAsync returns unexpected results, this may either be an issue with your code or on Roblox’s side.

2 Likes

Thank you for your help. I still have questions though.

  1. If I want to get the error code by using your method, how would I do that? Because you’re using return as the data to save/get.

  2. I actually really don’t understand my UpdateData function. Seems like I nailed it but can you explain the flow of it?

Question 1 has already been answered at the bottom of the post.

if success then
    -- Handle case of success
else
    -- Case of no success, output exception as a warning
    warn("DataStore Error:", result)
end

Two is best explained by reading up on UpdateAsync.

1 Like

I don’t understand this line.

Since this function validates the data, it should be used in favor of SetAsync() when there’s a chance that more than one server can edit the same data at the same time.

Pretty much does exactly what it says on the tin. Two or more servers may have the potential to update a DataStore key’s value, so UpdateAsync performs validation. That is, certain conditions need to be met for the update to be successful internally.

As a developer, the part most takeaway for you should be the last bit of that sentence, which is that UpdateAsync will properly handle cases of multiple servers updating data and allow you to transform (modify, change) the data directly. SetAsync, on the other hand, just indiscriminately sets a key’s value to whatever you pass and then it’s done. You don’t get the chance to change data.

A good way to think about DataStore functions and how to use them is also in terms of their names.

  • GetAsync will get the value of a key.
  • SetAsync will set the value of a key.
  • UpdateAsync will update the value of a key.

What’s the difference between setting and updating? An update is iterative upon a previous data point, which in UpdateAsync’s case is the single parameter given to the transform function (in your code sample, the transform function is UpdateData. A setting is making something into whatever you give it, but it’s not iterative and doesn’t care about what was there before.

I usually use UpdateAsync like this

local success, err = pcall(function() 
	DataStore:UpdateAsync(plr.UserId, function(OldValue) 
		return NewValue
	end
end) 

UpdateAsync considers old data before saving. So if you want to make something like, lets say a rebirth system. You can use UpdateAsync to check that the new data that should be saved is more than that of the old value. So if you check that the old value is more than the new value, you can just save the old value like:

DS:UpdateAsync(plr.UserId, function(OldValue) 
	if not OldValue then
		OldValue = 0
	end
	if NewValue > OldValue then
		return NewValue 
	else
		return OldValue 
	end
end) 
2 Likes