> ## Documentation Index
> Fetch the complete documentation index at: https://docs.selvut.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Timers And Async

> Script timers, handles, and blocking work.

The runtime exposes script-owned timers. Timers are cleared when a script unloads.

```lua theme={null}
local timerId = Script.setTimeout(function()
  Chat.log("ran later")
end, 1000)
```

```lua theme={null}
local intervalId = Script.setInterval(function(timerId)
  Chat.log("HP: " .. tostring(Player.health() or "?"))
  if Player.health() == nil then
    Script.clearInterval(timerId)
  end
end, 250)

Script.on("disable", function()
  Script.clearInterval(intervalId)
end)
```

Timer callbacks receive their timer id as the first argument. Use that id when an interval clears itself.

`Script.throttle(key, intervalMs)` is useful inside frequent events:

```lua theme={null}
Script.on("tick", function()
  if not Script.throttle("status", 1000) then return end
  Chat.log("TPS: " .. tostring(Client.tps()))
end)
```

`Script.cleanup(fn)` runs `fn` when the script runtime is disabled or unloaded. Use it to release external handles that are not already script-owned.

```lua theme={null}
local handle = Script.cleanup(function()
  Chat.log("cleaned up")
end)

-- Optional: prevent the cleanup callback from running later.
handle.remove()
```

`Script.thread(fn)` starts a daemon thread when the module is enabled. Use it for sleeps or long waits:

```lua theme={null}
Script.thread(function()
  while Script.enabled() do
    Time.sleep(1000)
    Chat.log("still running")
  end
end)
```

`Time.sleep` and `Time.sleepTicks` must not run in client-thread event callbacks or packet callbacks.

## Async

`Async` provides cancellable handles for worker functions, delayed callbacks, repeated callbacks, polling, and HTTP requests.

```lua theme={null}
local handle = Async.after(1000, function()
  Chat.log("ran later")
end)

Script.on("disable", function()
  handle.cancel()
end)
```

```lua theme={null}
Async.until(
  function() return Player.health() ~= nil and Player.health() < 10 end,
  function() Chat.log("low health") end,
  { intervalMs = 100, timeoutMs = 10000 }
)
```

```lua theme={null}
Async.request("https://example.com/status.json", function(response, error)
  if error then
    Chat.log(error)
    return
  end
  Chat.log("status: " .. tostring(response.status))
end)
```

`Async.request` accepts either a URL string or a request object:

| Field       | Description                         |
| ----------- | ----------------------------------- |
| `url`       | Request URL                         |
| `method`    | HTTP method, defaults to `GET`      |
| `headers`   | Header table                        |
| `body`      | Request body string                 |
| `timeoutMs` | Request timeout in milliseconds     |
| `binary`    | Return bytes instead of a text body |

Responses include `status`, `headers`, and `body` for text requests. For binary responses, pass `binary = true`; the response includes `bodyBytes` / `bytes` as a Java byte array plus `length`.

```lua theme={null}
Async.request({ url = "https://example.com/image.png", binary = true }, function(response, error)
  if error then return end
  local texture = Texture.loadBytes(response.bytes)
  Chat.log("downloaded " .. tostring(response.length) .. " bytes")
end)
```

Handles expose `id`, `kind`, `cancel()`, `running()`, `done()`, `failed()`, `error()`, and `result()`.
