How to properly utilize UpdateAsync

A lot of people have made posts on how to convert their systems to use UpdateAsync because UpdateAsync is better according to this post by @ForeverHD.

This post has a lot of already existing opinions and it does share a lot of what I am gonna say, however I’ll try packing in more updated and more specific details.


What is different with :UpdateAsync?

First of all, UpdateAsync will try to respect calls in the order they were called internally. This is not something you specifically see, but it is one of the big reasons UpdateAsync is better.

UpdateAsync is a Get-Set combo as stated by colbert in the replies of that post, UpdateAsync might sound as something to modify specific table values or something, that is not what it does. (You can make it do that, but it doesn’t WORK like that).

A UpdateAsync call will accept a key, and a function.

This function will be ran with the first argument being the data that is currently there, you can use that data for comparation, getting data from it… etc.

Example:


ds:UpdateAsync("userId", function(pastData)
    -- compare past data, get stuff from it, etc.
end)


From this call, you have to return back the data you wanna save, in case you return nil, it will keep the data as is, and not save and not do any write requests.

Another example:


local newData = {
    Cash = 100;
}

ds:UpdateAsync(player.UserId, function(pastData)
    return newData
end)

It will now save the new data, yes!

…but it doesn’t seem to do much different.

UpdateAsync like this is still better than normal SetAsync for saving data, the calls are still being ordered and managed and everything, but you should try adding as much functionality in this UpdateAsync call as possible.


What should I use UpdateAsync with?

If you didn’t notice already, you should be saving data with tables and making everything save under one key, if it’s related data. If you’re not doing that, learn that before thing to understand this. UpdateAsync has almost no use for people who save data separately.


UpdateAsync, like I said should be used to compare data, and decide if it should be saved or not.

A simple example, but pretty good and popular example is to compare version numbers.

An example would be:


ds:UpdateAsync(key, function(pastData)
    if pastData.DataVersion ~= player.DataVersion.Value then
        return nil --// Cancels saving data, doesn't do a write request.
    end

    return {
        --// Your data of course! ...
        DataVersion = player.DataVersion.Value + 1
    }
end)

This example is showing how you should keep a number which increases every save.

This number gets compared when trying to save, it needs to be the save as the number we got when we loaded data.

If it isn’t the same, don’t try saving any new data. Cancel that!

This example prevents trying to write data twice (overwriting).
This tecnic is popular and you should use it in your data saving system if UpdateAsync calls are being used!


There’s also tecnics like session locking, which use UpdateAsync in everything! You can learn about that by learning about ProfileService.

WHERE should I use UpdateAsync?

If you can integrate a good enough system, go for everything.

Yes everything, by that I mean loading data… everything. As long as that provides checking, functionality, then yes, go for it.

ProfileService does this already, not only because of how it kind of needs to, but also because it’s better and queueing these types of calls is mandatory.

When should I not use UpdateAsync?

This is pretty simple.

  • If you have anything that Is only ever written to once, and that overwriting doesn’t matter
  • Leaderboards (mostly the same for any OrderedDataStore use)
  • Just data that doesn’t overall matter.

If you’re using leaderboards, PLEASE DON’T USE UPDATEASYNC.

I would like to point that out because I see so many people blindly recommending using UpdateAsync in everything for no reason.

Leaderboards only mimic data! You should never care about data loss in a leaderboard anyway. Ordered DataStores can only save numbers anyway! You’re not even comparing ANYTHING.


Don’t use UpdateAsync calls for things that are supposed to be written to only once. (for the most part)

If you’re saving data that isn’t a table anyway, for the most part, you’re not able to compare data, therefore UpdateAsync turns mostly useless.


Additional info:

  1. Don’t yield (wait, Async calls, etc) inside UpdateAsync functions.
  2. Try comparing the most facts about data you can, if there was supposed to be there data, yet there isn’t for example, you can do something about it.
  3. … Might add extra info later.
9 Likes

Huh?

I don’t quite understand what’s that since I never found that property…

I still have no idea how I can use UpdateAsync.

It really depends on what you’re doing. If it’s basic player saving data, yes probably you should use UpdateAsync.

UpdateAsync is simply a get and set combo, you can compare the data and DECIDE if you wanna save it.

There’s where the entire comparing “versions” comes from.

If you need help with the actual API and how to use it, you can send me a DM, i don’t have anything to do :P

How did you know there’s this property?

1 Like

This is not a property inside normal datastores, that’s something you add to the table you’re saving, this number increases every time you save, when saving that number should be the same as the one when you loaded the data in the first place. If it is the same, save everything and increase that number by 1. So it’s more of a “tecnic”.

So by any chance, if this value somehow gets updated, that’s why we use conditional statement to check if it’s correct?

Yes, in this case you only save in case that value is the same as when you loaded the data.

So an example would be like:

— checking/loading data

if result then
    — load data
else
    — retry and stuffs
end

— saving data

local tableData = {
    ["Version"] = plr.leaderstats.Version.Value
    — your data
}
DS:UpdateAsync(plr.UserId, function(pastData)
    if pastData.Version ~= tableData["Version"] then
        warn("Data Version is not the same.")
        return nil
    else
        tableData["Version"] += 1
        return tableData
    end
end)
1 Like

Exactly! Only thing you would do differently here is not put the value inside leaderstats, I would just keep it in the player. (You probably know that already)

You mean I shouldn’t put the Version inside the leaderstats?

Yes, you should just keep it inside the player object, not leaderstats. Anything on leaderstats shows up to everyone on the playerlist so.

1 Like

Thank you. This tutorial finally gave me a great clue on how to use UpdateAsync(). Thank you so much.

gonna bookmark this

1 Like

One last question, if the version doesn’t match, what should I do before I return nil! Because if I just return nil after the player leaves the game and then I code it to save data on exit, the player will be mad that their data didn’t save… so how can I make sure?

If you’re auto saving, then I would kick the player. On player leaving, I would have just not tried saving.

1 Like

Thing is, the data would probably have been already saved. If it was different, that very likely means that maybe it was saved already, that the data was written to in a way.

This line wont work if the past data is nil (player joins in for the first time), so it will always error.


Other thing for update async, generally I like to put in a check to see if their current value is greater than their previous. This means that if for some reason they have lost their data, it doesn’t get overridden

local success, err = DataStore:UpdateAsync(key, function(old)
    if (not old) or (player.DataVersion.Value > old.DataVersion.Value)
        return {player.DataVersion.Value};
    else
        warn("PlayerData update error"); 
        return nil;
    end
end)

I would also create some other datastore functions. One to reduce player data, and one to reset/set player data (with setAsync, That’s the point of set async)

1 Like

Yes. That’s why it’s pseudo-code.

That’s just wrong… that tecnic is that you don’t overwrite data, you’re assuming the data is already overwritten? That’s pretty odd. This “tecnic” is mostly so that BindToClose() calls for example, don’t save twice from PlayerRemoving. The only thing way is to compare if it’s the same in this case.

This tutorial is not as informative, since tons of resources have explained how to properly use UpdateAsync, though it would be good for an beginner to understand how to properly save data. This tutorial also doesn’t explain how to efficiently load data with UpdateAsync, nor tells how it respects “incoming calls”. Instead this tutorial; Stop using SetAsync explains it all, this tutorial for the most part has no point or usefulness except the only thing you did was just re-phrase the old tutorial.

This function will be ran with the first argument being the data that is currently there, you can use that data for comparation, getting data from it… etc.

This is incorrect, the callback will always be called despite if there was no data for that key, it will only be not be called is when UpdateAsync fails.

This is incorrect, I didn’t say it wouldn’t run if data wasn’t there.

If data is nil, it’s still gonna get “passed as an argument” obviously.

I do, I don’t talk about it’s internal system or something, no one knows that but:

Anyways it’s supposed to be for beginners for basic use, and the post you linked is a big vague on how it works for anyone trying to understand. I still see A LOT of newer scripters NOT knowing where to use it, and blindly recommend it for random stuff.

This is incorrect, I didn’t say it wouldn’t run if data wasn’t there.

That isn’t incorrect, you did say that. I suggest you-read what you said.

First of all, UpdateAsync will try to respect calls in the order they were called internally. This is not something you specifically see, but it is one of the big reasons UpdateAsync is better.

This isn’t sufficient information.