Task+ - promise-driven wrapper for the native task library

task+

A simple and useful upgrade for the native task library.

task+ is a lightweight module designed to upgrade Roblox’s native task functions. It protects your game from multi-threading overheads and lets you link your functions into clean chains (main feature) so you don’t have to write messy code.

You can set it up like this:

const task = require(path.to.stupid["task+"]);

What it does

  • Function Chaining: No more messy nested code. You can run functions one after another using simple methods like :andCall(), :andThen(), :andCatch(), and :andClear().
  • Safe Multi-threading: The module automatically knows whether it is in a parallel or serial thread, protecting your game from redundant task scheduler overheads (like setting lua_State to serial when it is already synchronized).
  • Optimized Performance: All chained functions run sequentially inside a single thread context.
  • Global Thread Manager: Using task.Threads, you can check whether a thread belongs to task+, see if it is done, or completely wipe it from memory from any other script.
  • Full Autocomplete: Complete support for Luau typing so you won’t get any annoying yellow lines in Roblox Studio, guh.

Code Examples

1. Basic Chaining (Run functions one after another)

local function printHello()
	print("Hello")
end

local function printWorld()
	print("World")
end

--// waits 5 seconds, prints "Hello", and prints "World" right after it finishes
task.delay(5, printHello):andCall(printWorld)

2. Passing Data & Catching Errors (Simple Pipeline)

local function getNumber()
	return 10 --// passes this number to the next step
end

task.spawn(getNumber)
	:andThen(function(myNumber)
		print("Got number:", myNumber) --// prints 10
		return myNumber * 2 --// passes 20 to the next step
	end)
	:andThen(function(newNumber)
		if newNumber > 15 then
			error("Number is too big!") --// triggers an error
		end
	end)
	:andCatch(function(err)
		warn("Caught a bug:", err) --// runs because an error happened above
	end)
	:andClear() --// completely cleans up the memory

3. Safe Calls (Catching errors instantly)

local function brokenFunction()
	error("imagine error here")
end

--// ':andSafeCall' runs a function safely and puts results straight into variables
local success, result = task.spawn(brokenFunction):andSafeCall(function()
	print("This runs inside pcall")
end)

print(success) --// prints false

4. Managing Threads from the Outside

local myThread = task.delay(10, function() print("Done") end)

--// check if this is a task+ object and if it is completed from another script
if task.Threads.IsTaskPlus(myThread) and task.Threads.IsCompleted(myThread) then
	task.Threads.Clear(myThread) --// Forcefully deletes the thread from memory
end

Thread Synchronization Note

task+ utilizes a fast SharedTable trap to check thread states instantly:

if parallel_states.IsParallel() then 
    task.synchronize()
end

The built-in thread context changers (task.synchronize / task.desynchronize) are already able to detect the current thread context automatically.


Why task+ instead of standard Promises?

Standard Promise libraries (like evaera’s) are incredibly robust and reliable. However, they are heavy because they port the entire JavaScript Promise ecosystem into Roblox.

Here is why you might want to use task+ instead:

  1. Thread Switch Optimization: Calling task.synchronize() or task.desynchronize() multiple times causes microsecond overhead because the engine forces context switches through the Task Scheduler. task+ uses a fast SharedTable trap to check the thread state first, executing the synchronization methods only when actually necessary to save CPU cycles.

  2. Single-Thread Performance: Instead of spawning a new coroutine for every single link in the chain (why?), task+ executes chained methods sequentially within the same thread context. This significantly reduces Garbage Collector and Task Scheduler pressure.

  3. Native Luau Feel: With methods like :andSafeCall(), you can instantly return values back into standard Lua tuples (local success, result = ...) instead of being forced into continuous callback nesting.

Links

2 Likes

This has to be August Fools, 10/10

I don’t really understand sarcasm is it or not, but thanks, I guess.