Skip to main content

Concur

Concurrency class for helping run tasks concurrently. In other words, Concur allows developers to watch coroutines/threads. Completion status, returned values, and errors can all be tracked.

For instance, Concur could be used to concurrently save all player data at the same time when the game closes down:

game:BindToClose(function()
	local all = {}
	for _,player in Players:GetPlayers() do
		local save = Concur.spawn(function()
			DoSomethingToSaveData(player)
		end)
		table.insert(all, save)
	end
	local allConcur = Concur.all(all)
	allConcur:Await()
end)

Types

Errors

interface Errors {
Stopped"Stopped"
Timeout"Timeout"
}

Properties

Errors

This item is read only and cannot be modified. Read Only
Concur.Errors: Errors

Functions

spawn

Concur.spawn(
fnAnyFn,
...any
) → Concur

Spawns the function using task.spawn.

local c = Concur.spawn(function()
	task.wait(5)
	return "Hello!"
end)

c:OnCompleted(function(err, msg)
	if err then
		error(err)
	end
	print(msg) --> Hello!
end))

defer

Concur.defer(
fnAnyFn,
...any
) → Concur

Same as Concur.spawn, but uses task.defer internally.

delay

Concur.delay(
delayTimenumber,
fnAnyFn,
...any
) → Concur

Same as Concur.spawn, but uses task.delay internally.

value

Concur.value(valueany) → Concur

Resolves to the given value right away.

local val = Concur.value(10)
val:OnCompleted(function(v)
	print(v) --> 10
end)

event

Concur.event(
predicate((...any) → boolean)?
) → ()

Completes the Concur instance once the event is fired and the predicate function returns true (if no predicate is given, then completes once the event first fires).

The Concur instance will return the values given by the event.

-- Wait for next player to touch an object:
local touch = Concur.event(part.Touched, function(toucher)
	return Players:GetPlayerFromCharacter(toucher.Parent) ~= nil
end)

touch:OnCompleted(function(err, toucher)
	print(toucher)
end)

all

Concur.all(concurs{Concur}) → Concur

Completes once all Concur instances have been completed. All values will be available in a packed table in the same order they were passed.

local c1 = Concur.spawn(function()
	return 10
end)

local c2 = Concur.delay(0.5, function()
	return 15
end)

local c3 = Concur.value(20)

local c4 = Concur.spawn(function()
	error("failed")
end)

Concur.all({c1, c2, c3}):OnCompleted(function(err, values)
	print(values) --> {{nil, 10}, {nil, 15}, {nil, 20}, {"failed", nil}}
end)

first

Concur.first(concurs{Concur}) → Concur

Completes once the first Concur instance is completed without an error. All other Concur instances are then stopped.

local c1 = Concur.delay(1, function()
	return 10
end)

local c2 = Concur.delay(0.5, function()
	return 5
end)

Concur.first({c1, c2}):OnCompleted(function(err, num)
	print(num) --> 5
end)

Stop

Concur:Stop() → ()

Stops the Concur instance. The underlying thread will be cancelled using task.cancel. Any bound OnCompleted functions or threads waiting with Await will be completed with the error Concur.Errors.Stopped.

local c = Concur.spawn(function()
	for i = 1,10 do
		print(i)
		task.wait(1)
	end
end)

task.wait(2.5)
c:Stop() -- At this point, will have only printed 1 and 2

IsCompleted

Concur:IsCompleted() → boolean

Check if the Concur instance is finished.

Await

This is a yielding function. When called, it will pause the Lua thread that called the function until a result is ready to be returned, without interrupting other scripts. Yields
Concur:Await(timeoutnumber?) → (
Error,
...any?
)

Yields the calling thread until the Concur instance is completed:

local c = Concur.delay(5, function()
	return "Hi"
end)

local err, msg = c:Await()
print(msg) --> Hi

The Await method can be called after the Concur instance has been completed too, in which case the completed values will be returned immediately without yielding the thread:

local c = Concur.spawn(function()
	return 10
end)

task.wait(5)
-- Called after 'c' has been completed, but still captures the value:
local err, num = c:Await()
print(num) --> 10

It is always good practice to make sure that the err value is handled by checking if it is not nil:

local c = Concur.spawn(function()
	error("failed")
end)

local err, value = c:Await()

if err ~= nil then
	print(err) --> failed
	-- Handle error `err`
else
	-- Handle `value`
end

This will stop awaiting if the Concur instance was stopped too, in which case the err will be equal to Concur.Errors.Stopped:

local c = Concur.delay(10, function() end)
c:Stop()
local err = c:Await()
if err == Concur.Errors.Stopped then
	print("Was stopped")
end

An optional timeout can be given, which will return the Concur.Errors.Timeout error if timed out. Timing out does not stop the Concur instance, so other callers to Await or OnCompleted can still grab the resulting values.

local c = Concur.delay(10, function() end)
local err = c:Await(1)
if err == Concur.Errors.Timeout then
	-- Handle timeout
end

OnCompleted

Concur:OnCompleted(
fn(
Error,
...any?
) → (),
timeoutnumber?
) → () → ()

Calls the given function once the Concur instance is completed:

local c = Concur.delay(5, function()
	return "Hi"
end)

c:OnCompleted(function(err, msg)
	print(msg) --> Hi
end)

A function is returned that can be used to unbind the function to no longer fire when the Concur instance is completed:

local c = Concur.delay(5, function() end)
local unbind = c:OnCompleted(function()
	print("Completed")
end)
unbind()
-- Never prints "Completed"

The OnCompleted method can be called after the Concur instance has been completed too, in which case the given function will be called immediately with the completed values:

local c = Concur.spawn(function()
	return 10
end)

task.wait(5)
-- Called after 'c' has been completed, but still captures the value:
c:OnCompleted(function(err, num)
	print(num) --> 10
end)

It is always good practice to make sure that the err value is handled by checking if it is not nil:

local c = Concur.spawn(function()
	error("failed")
end)

c:OnCompleted(function(err, value)
	if err ~= nil then
		print(err) --> failed
		-- Handle error `err`
		return
	end
	-- Handle `value`
end)

This will call the function if the Concur instance was stopped too, in which case the err will be equal to Concur.Errors.Stopped:

local c = Concur.delay(10, function() end)
c:OnCompleted(function(err)
	if err == Concur.Errors.Stopped then
		print("Was stopped")
	end
end)
c:Stop()

An optional timeout can also be supplied, which will call the function with the Concur.Errors.Timeout error:

local c = Concur.delay(10, function() end)
c:OnCompleted(function(err)
	if err == Concur.Errors.Timeout then
		-- Handle timeout
	end
end, 1)
Show raw api
{
    "functions": [
        {
            "name": "spawn",
            "desc": "Spawns the function using `task.spawn`.\n\n```lua\nlocal c = Concur.spawn(function()\n\ttask.wait(5)\n\treturn \"Hello!\"\nend)\n\nc:OnCompleted(function(err, msg)\n\tif err then\n\t\terror(err)\n\tend\n\tprint(msg) --> Hello!\nend))\n```",
            "params": [
                {
                    "name": "fn",
                    "desc": "",
                    "lua_type": "AnyFn"
                },
                {
                    "name": "...",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Concur\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 100,
                "path": "modules/concur/init.luau"
            }
        },
        {
            "name": "defer",
            "desc": "Same as `Concur.spawn`, but uses `task.defer` internally.",
            "params": [
                {
                    "name": "fn",
                    "desc": "",
                    "lua_type": "AnyFn"
                },
                {
                    "name": "...",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Concur\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 110,
                "path": "modules/concur/init.luau"
            }
        },
        {
            "name": "delay",
            "desc": "Same as `Concur.spawn`, but uses `task.delay` internally.",
            "params": [
                {
                    "name": "delayTime",
                    "desc": "",
                    "lua_type": "number"
                },
                {
                    "name": "fn",
                    "desc": "",
                    "lua_type": "AnyFn"
                },
                {
                    "name": "...",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Concur\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 120,
                "path": "modules/concur/init.luau"
            }
        },
        {
            "name": "value",
            "desc": "Resolves to the given value right away.\n\n```lua\nlocal val = Concur.value(10)\nval:OnCompleted(function(v)\n\tprint(v) --> 10\nend)\n```",
            "params": [
                {
                    "name": "value",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Concur\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 139,
                "path": "modules/concur/init.luau"
            }
        },
        {
            "name": "event",
            "desc": "Completes the Concur instance once the event is fired and the predicate\nfunction returns `true` (if no predicate is given, then completes once\nthe event first fires).\n\nThe Concur instance will return the values given by the event.\n\n```lua\n-- Wait for next player to touch an object:\nlocal touch = Concur.event(part.Touched, function(toucher)\n\treturn Players:GetPlayerFromCharacter(toucher.Parent) ~= nil\nend)\n\ntouch:OnCompleted(function(err, toucher)\n\tprint(toucher)\nend)\n```",
            "params": [
                {
                    "name": "event",
                    "desc": "",
                    "lua_type": "RBXScriptSignal"
                },
                {
                    "name": "predicate",
                    "desc": "",
                    "lua_type": "((...any) -> boolean)?"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 163,
                "path": "modules/concur/init.luau"
            }
        },
        {
            "name": "all",
            "desc": "Completes once _all_ Concur instances have been completed. All values\nwill be available in a packed table in the same order they were passed.\n\n```lua\nlocal c1 = Concur.spawn(function()\n\treturn 10\nend)\n\nlocal c2 = Concur.delay(0.5, function()\n\treturn 15\nend)\n\nlocal c3 = Concur.value(20)\n\nlocal c4 = Concur.spawn(function()\n\terror(\"failed\")\nend)\n\nConcur.all({c1, c2, c3}):OnCompleted(function(err, values)\n\tprint(values) --> {{nil, 10}, {nil, 15}, {nil, 20}, {\"failed\", nil}}\nend)\n```",
            "params": [
                {
                    "name": "concurs",
                    "desc": "",
                    "lua_type": "{ Concur }"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Concur\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 215,
                "path": "modules/concur/init.luau"
            }
        },
        {
            "name": "first",
            "desc": "Completes once the first Concur instance is completed _without an error_. All other Concur\ninstances are then stopped.\n\n```lua\nlocal c1 = Concur.delay(1, function()\n\treturn 10\nend)\n\nlocal c2 = Concur.delay(0.5, function()\n\treturn 5\nend)\n\nConcur.first({c1, c2}):OnCompleted(function(err, num)\n\tprint(num) --> 5\nend)\n```",
            "params": [
                {
                    "name": "concurs",
                    "desc": "",
                    "lua_type": "{ Concur }"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Concur\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 259,
                "path": "modules/concur/init.luau"
            }
        },
        {
            "name": "Stop",
            "desc": "Stops the Concur instance. The underlying thread will be cancelled using\n`task.cancel`. Any bound `OnCompleted` functions or threads waiting with\n`Await` will be completed with the error `Concur.Errors.Stopped`.\n\n```lua\nlocal c = Concur.spawn(function()\n\tfor i = 1,10 do\n\t\tprint(i)\n\t\ttask.wait(1)\n\tend\nend)\n\ntask.wait(2.5)\nc:Stop() -- At this point, will have only printed 1 and 2\n```",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 310,
                "path": "modules/concur/init.luau"
            }
        },
        {
            "name": "IsCompleted",
            "desc": "Check if the Concur instance is finished.",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean\n"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 325,
                "path": "modules/concur/init.luau"
            }
        },
        {
            "name": "Await",
            "desc": "Yields the calling thread until the Concur instance is completed:\n\n```lua\nlocal c = Concur.delay(5, function()\n\treturn \"Hi\"\nend)\n\nlocal err, msg = c:Await()\nprint(msg) --> Hi\n```\n\nThe `Await` method can be called _after_ the Concur instance\nhas been completed too, in which case the completed values\nwill be returned immediately without yielding the thread:\n\n```lua\nlocal c = Concur.spawn(function()\n\treturn 10\nend)\n\ntask.wait(5)\n-- Called after 'c' has been completed, but still captures the value:\nlocal err, num = c:Await()\nprint(num) --> 10\n```\n\nIt is always good practice to make sure that the `err` value is handled\nby checking if it is not nil:\n\n```lua\nlocal c = Concur.spawn(function()\n\terror(\"failed\")\nend)\n\nlocal err, value = c:Await()\n\nif err ~= nil then\n\tprint(err) --> failed\n\t-- Handle error `err`\nelse\n\t-- Handle `value`\nend\n```\n\nThis will stop awaiting if the Concur instance was stopped\ntoo, in which case the `err` will be equal to\n`Concur.Errors.Stopped`:\n\n```lua\nlocal c = Concur.delay(10, function() end)\nc:Stop()\nlocal err = c:Await()\nif err == Concur.Errors.Stopped then\n\tprint(\"Was stopped\")\nend\n```\n\nAn optional timeout can be given, which will return the\n`Concur.Errors.Timeout` error if timed out. Timing out\ndoes _not_ stop the Concur instance, so other callers\nto `Await` or `OnCompleted` can still grab the resulting\nvalues.\n\n```lua\nlocal c = Concur.delay(10, function() end)\nlocal err = c:Await(1)\nif err == Concur.Errors.Timeout then\n\t-- Handle timeout\nend\n```",
            "params": [
                {
                    "name": "timeout",
                    "desc": "",
                    "lua_type": "number?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Error"
                },
                {
                    "desc": "",
                    "lua_type": "...any?"
                }
            ],
            "function_type": "method",
            "yields": true,
            "source": {
                "line": 402,
                "path": "modules/concur/init.luau"
            }
        },
        {
            "name": "OnCompleted",
            "desc": "Calls the given function once the Concur instance is completed:\n\n```lua\nlocal c = Concur.delay(5, function()\n\treturn \"Hi\"\nend)\n\nc:OnCompleted(function(err, msg)\n\tprint(msg) --> Hi\nend)\n```\n\nA function is returned that can be used to unbind the function to\nno longer fire when the Concur instance is completed:\n\n```lua\nlocal c = Concur.delay(5, function() end)\nlocal unbind = c:OnCompleted(function()\n\tprint(\"Completed\")\nend)\nunbind()\n-- Never prints \"Completed\"\n```\n\nThe `OnCompleted` method can be called _after_ the Concur instance\nhas been completed too, in which case the given function will be\ncalled immediately with the completed values:\n\n```lua\nlocal c = Concur.spawn(function()\n\treturn 10\nend)\n\ntask.wait(5)\n-- Called after 'c' has been completed, but still captures the value:\nc:OnCompleted(function(err, num)\n\tprint(num) --> 10\nend)\n```\n\nIt is always good practice to make sure that the `err` value is handled\nby checking if it is not nil:\n\n```lua\nlocal c = Concur.spawn(function()\n\terror(\"failed\")\nend)\n\nc:OnCompleted(function(err, value)\n\tif err ~= nil then\n\t\tprint(err) --> failed\n\t\t-- Handle error `err`\n\t\treturn\n\tend\n\t-- Handle `value`\nend)\n```\n\nThis will call the function if the Concur instance was stopped\ntoo, in which case the `err` will be equal to\n`Concur.Errors.Stopped`:\n\n```lua\nlocal c = Concur.delay(10, function() end)\nc:OnCompleted(function(err)\n\tif err == Concur.Errors.Stopped then\n\t\tprint(\"Was stopped\")\n\tend\nend)\nc:Stop()\n```\n\nAn optional timeout can also be supplied, which will call the\nfunction with the `Concur.Errors.Timeout` error:\n\n```lua\nlocal c = Concur.delay(10, function() end)\nc:OnCompleted(function(err)\n\tif err == Concur.Errors.Timeout then\n\t\t-- Handle timeout\n\tend\nend, 1)\n```",
            "params": [
                {
                    "name": "fn",
                    "desc": "",
                    "lua_type": "(Error, ...any?) -> ()"
                },
                {
                    "name": "timeout",
                    "desc": "",
                    "lua_type": "number?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "() -> ()\n"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 517,
                "path": "modules/concur/init.luau"
            }
        }
    ],
    "properties": [
        {
            "name": "Errors",
            "desc": "",
            "lua_type": "Errors",
            "readonly": true,
            "source": {
                "line": 49,
                "path": "modules/concur/init.luau"
            }
        }
    ],
    "types": [
        {
            "name": "Errors",
            "desc": "",
            "fields": [
                {
                    "name": "Stopped",
                    "lua_type": "\"Stopped\"",
                    "desc": ""
                },
                {
                    "name": "Timeout",
                    "lua_type": "\"Timeout\"",
                    "desc": ""
                }
            ],
            "source": {
                "line": 43,
                "path": "modules/concur/init.luau"
            }
        }
    ],
    "name": "Concur",
    "desc": "Concurrency class for helping run tasks concurrently. In other words, Concur allows\ndevelopers to watch coroutines/threads. Completion status, returned values, and\nerrors can all be tracked.\n\nFor instance, Concur could be used to concurrently save all player data\nat the same time when the game closes down:\n\n```lua\ngame:BindToClose(function()\n\tlocal all = {}\n\tfor _,player in Players:GetPlayers() do\n\t\tlocal save = Concur.spawn(function()\n\t\t\tDoSomethingToSaveData(player)\n\t\tend)\n\t\ttable.insert(all, save)\n\tend\n\tlocal allConcur = Concur.all(all)\n\tallConcur:Await()\nend)\n```",
    "source": {
        "line": 34,
        "path": "modules/concur/init.luau"
    }
}