A lightweight, cancellation-aware Promise implementation designed for asynchronous workflows, resource cleanup, and structured concurrency in Luau/Roblox environments. Developed by ethandevelopz.
This library provides a Promise abstraction for handling asynchronous operations with explicit fulfillment, rejection, cancellation, and chaining.
It is designed around Luau's multiple return values and Roblox's coroutine-based execution model, with consumer-aware cancellation to prevent shared asynchronous operations from being cancelled prematurely.
- Promise Chaining: Chain asynchronous operations with
andThen,catch, andfinally. - Cancellation: Promises can be cancelled with cancellation hooks for cleaning up underlying resources.
- Consumer-Aware Cancellation: Shared Promises track active consumers, allowing one cancelled consumer to leave other consumers unaffected.
- Multiple Return Values: Native Luau multiple returns are preserved throughout Promise chains.
- Thenable Assimilation: Promises can adopt other Promise-like values and function-based thenables.
- Promise Combinators: Includes
all,race,any,allSettled, andsomewith cancellation-aware behavior. - Collection Utilities:
map,filter, andeachapply async transformations and side effects to arrays of values or Promises. - Roblox Utilities: Includes
delay,timeout,retry,defer, andfromEventfor common asynchronous workflows. - Interop Utilities:
tryandpromisifymake it easy to bring synchronous code and callback-style APIs into the Promise world. - Coroutine Integration:
awaitallows a coroutine to yield until a Promise settles without blocking other Roblox execution. - Error Handling: Rejections propagate through chains and can be handled with
catchor converted into thrown errors withexpect. - Resource Cleanup:
onCancelhooks allow asynchronous operations to clean up connections, timers, and other resources when cancelled.
See documentation.md for the complete API reference. Summary below.
promise.new(function(resolve, reject, onCancel)
-- asynchronous work
resolve(value)
end)Call resolve(...) when the operation succeeds and reject(...) when it fails.
promise.try(function(a, b)
return a + b
end, 2, 3)promise.try runs a synchronous or asynchronous callback and wraps its result (or thrown error) in a Promise.
local readFileAsync = promise.promisify(function(path, callback)
callback(nil, readFile(path))
end)
readFileAsync('data.json'):andThen(print)promise.promisify converts a callback-taking function (...args, callback) into a function that returns a Promise, using the callback(err, ...results) convention: a non-nil err rejects, otherwise the remaining values fulfill. That convention is a choice made by this library, not a Roblox or Luau standard, so callback needs to actually report errors this way for rejections to work.
promise.defer(function()
return computeExpensiveThing()
end)promise.defer schedules a callback with task.defer and resolves with its result.
promise.new(function(resolve)
resolve('hello')
end)
:andThen(function(value)
print(value)
end)
:catch(function(reason)
warn(reason)
end)fetchUser(id)
:tap(function(user)
print('fetched', user.name)
end)
:andThen(function(user)
return user.name
end)tap runs a side effect on fulfillment and passes the original value(s) through unchanged.
local success, value = myPromise:await()
if success then
print(value)
endlocal request = getData()
request:onCancel(function()
-- cleanup
end)
request:cancel()promise.all({
firstPromise,
secondPromise,
thirdPromise,
}):andThen(function(results)
print('all completed')
end)all, race, any, and some automatically cancel their remaining subscriptions when their outcome is determined.
promise.some({firstPromise, secondPromise, thirdPromise}, 2):andThen(function(results)
print('first 2 results are in')
end)some resolves once a given number of Promises in a list have fulfilled, rejecting only once reaching that number becomes impossible.
promise.map({1, 2, 3}, function(value)
return value * 2
end):andThen(print)
promise.filter({1, 2, 3, 4}, function(value)
return value % 2 == 0
end):andThen(print)
promise.each({1, 2, 3}, function(value, index)
print(index, value)
end)map and filter resolve their list entries (which may themselves be Promises) concurrently and reject/cancel the rest as soon as one fails. each processes entries strictly in order, waiting for each one before moving to the next.
The library includes a dedicated test suite covering Promise state transitions, cancellation, chaining, thenable assimilation, cleanup, combinators, collection utilities, timeouts, retries, cancellation races, and the interop utilities.
Current test status:
62 passed, 0 failed
Place the Promise ModuleScript somewhere accessible to your scripts, such as ReplicatedStorage.
local promise = require(game:GetService('ReplicatedStorage').Promise)The library has no required external dependencies.
- Discord:
ethanmracing - Roblox: ethandevelopz Profile
- GitHub: ethandevelopz