Codectionary / Developer documentation / Luau

UpdateAsync and concurrent writes

UpdateAsync lets a non-yielding callback transform the latest value. It may invoke that callback again after a conflict. This example saves a monotonic highest-lesson record with max; that merge rule is appropriate for progress that never decreases, not balances, inventories or resets.

Syntax

store:UpdateAsync(key, function(current)
    -- Return the merged record, or nil to cancel. Do not yield.
end)

Examples

Merge monotonic progress without overwriting newer data

ModuleScript named LessonSave in ServerScriptService of the test experience. It accepts only a ready session from the load flow. Invalid stored data cancels the update; it is never replaced with defaults. No rewards, waits or network calls belong inside the callback.

local store = game:GetService("DataStoreService"):GetDataStore("LessonProgress_TEST_v1")
local LessonSave = {}
local function valid(value)
    return type(value) == "number" and value == value and value >= 0 and value <= 32 and value % 1 == 0
end
function LessonSave.save(userId, session)
    if not session or not session.ready or not session.data or not valid(session.data.highestLesson) then
        return false, "Session is not ready"
    end
    local highest = session.data.highestLesson -- snapshot before yielding
    local ok, saved = pcall(function()
        return store:UpdateAsync("player:" .. tostring(userId), function(current)
            if current == nil then return {version = 1, highestLesson = highest} end
            if type(current) ~= "table" or current.version ~= 1 or not valid(current.highestLesson) then return nil end
            return {version = 1, highestLesson = math.max(current.highestLesson, highest)}
        end)
    end)
    if not ok or saved == nil then return false, "Save failed or cancelled" end
    return true
end
return LessonSave

Wire an explicit test save

Script in ServerScriptService with LessonLoad and LessonSave from these pages. Use a dedicated test key only. Loading failure returns before saving. This saves unchanged loaded data to demonstrate the flow; a real progress increase must come from validated server gameplay.

local LessonLoad = require(script.Parent:WaitForChild("LessonLoad"))
local LessonSave = require(script.Parent:WaitForChild("LessonSave"))
local testUserId = 12345 -- dedicated test experience/key only
local data, reason = LessonLoad.load(testUserId)
if not data then warn(reason); return end
local session = {ready = true, data = data}
local ok, message = LessonSave.save(testUserId, session)
if not ok then warn(message) end

Best practices

  • UpdateAsync alone does not prevent stale session overwrites if you return a stale full snapshot; choose a deliberate merge or session-locking strategy.
  • Keep callbacks deterministic and free of side effects because they can run more than once.
  • This teaching schema has no deletion/reset, trading, currency or session-lock protocol. Do not extend max merging to those operations.

At a glance

Purpose
Typed scripting and Roblox development
File extension
.luau
Runs in
Luau host; Roblox engine examples require Roblox Studio
Usually used with
Roblox APIs and Studio

Specifications & further reading

Related Luau documentation