Promise → Chaining

Chaining

Then, Catch, and Finally let you compose async steps without nested callbacks. Each method returns the same Promise, so you can chain them in sequence.

Then

promise:Then(fn: (value: any) → any?) → Promise

Called when the Promise resolves. The return value of fn becomes the resolved value of the next step in the chain. If fn returns a Promise, the chain waits for that Promise to settle before continuing.

network:GetAsync("shop/catalogue")
    :Then(function(res)
        return res.data.items        -- pass items to the next Then
    end)
    :Then(function(items)
        populateShop(items)         -- receives the return value from above
    end)

Catch

promise:Catch(fn: (err: any) → any?) → Promise

Called when any step in the chain rejects (errors or times out). A single :Catch() at the end of the chain handles errors from all preceding steps — you don't need one after every :Then().

network:GetAsync("player/data")
    :Then(function(res) UI:Load(res.data) end)
    :Then(function() UI:Show() end)
    :Catch(function(err)
        -- catches errors from any Then above
        warn(err.message)
        UI:ShowError("Failed to load player data")
    end)

Finally

promise:Finally(fn: () → ()) → Promise

Called regardless of whether the Promise resolved or rejected. Use it for cleanup that must always run — hiding a loading spinner, unlocking a button, etc. The return value of fn is ignored.

LoadingSpinner.Visible = true

network:GetAsync("leaderboard")
    :Then(function(res) LeaderboardUI:Populate(res.data) end)
    :Catch(function(err) warn(err.message) end)
    :Finally(function()
        LoadingSpinner.Visible = false   -- always runs
    end)

How errors propagate

When a step rejects — either because the network returned an error/timeout, or because a :Then() callback threw — the rejection skips all subsequent :Then() steps and jumps to the first :Catch() in the chain. After :Catch() handles the error, the chain continues normally (resolved) unless :Catch() itself throws.

network:GetAsync("missing/route")
    :Then(function(res)
        -- SKIPPED if the request failed
        UI:Load(res.data)
    end)
    :Then(function()
        -- also SKIPPED
        UI:Show()
    end)
    :Catch(function(err)
        -- runs here with the NetworkResponse that has type == "error"
        warn("Error:", err.type, err.message)
    end)
    :Finally(function()
        -- ALWAYS runs
        LoadingSpinner.Visible = false
    end)

Sequential requests

Return a Promise from inside :Then() to chain network calls in order. The outer chain does not advance until the returned Promise settles.

network:GetAsync("shop/catalogue")
    :Then(function(res)
        ShopUI:Load(res.data)
        return network:GetAsync("player/data")   -- wait for this too
    end)
    :Then(function(res)
        CoinsUI:Set(res.data.coins)               -- player/data result
    end)
    :Catch(function(err)
        UI:ShowError(err.message)
    end)

See also

← Promise  ·  Creation | what resolves and rejects  ·  Async / Promise API | methods that return Promises  ·  Promise Guide | parallel merge and advanced patterns