Lua implementation of the Promises/A+ standard (not completely compliant due to some differences between JS and Lua) for Garry's Mod. One big difference is the next
is used instead of then
since then
is a keyword in Lua. If you have never used promises before, you should check out this explanation. While this isn't JavaScript, the concepts are the same.
Place the deferred.lua
file somewhere and include it with
include("path/to/deferred.lua")
Basic usage:
local function fetch(url)
local d = deferred.new()
http.Fetch(url, function(body, size, headers, code)
d:resolve(body)
end, function(err)
d:reject(err)
end)
return d
end
fetch("https://google.com")
:next(function(body)
print("Body is: ", body)
end)
Chaining promises
fetch("https://google.com")
:next(function(body)
print("Body is: ", body)
return #body
end)
:next(function(length)
print("And the length is: ", length)
end)
Handling rejection
fetch("https://google.com")
:next(function(body)
print("Body is: ", body)
return #body
end)
:next(function(length)
print("And the length is: ", length)
end)
:catch(function(err)
print("Oops!", err)
end)
or
fetch("https://google.com")
:next(function(body)
print("Body is: ", body)
return #body
end)
:next(function(length)
print("And the length is: ", length)
end)
:next(nil, function(err)
print("Oops!", err)
end)
From MDN:
The
Promise
object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.
This method adds a rejection handler to the promise chain. It is equivalent to doing Promise:next(nil, onRejected)
.
See the Promise/A+ then
method.
Rejects the promise with the given reason.
Resolves the promise to the given value.
This is the actual library for creating promises.
Returns a Promise
that resolves to a table where the ith element is what the ith promise in promises
resolves to after all the promises have been resolved. The promise will reject with the reason of the first promise within promises
to reject. If promises
is empty, then the returned promise resolves to an empty table {}
.
Example:
local function fetch(url)
local d = deferred.new()
http.Fetch(url, function(body)
d:resolve(body)
end, function(err)
d:reject(err)
end)
return d
end
deferred.all({ fetch("https://google.com"), fetch("https://github.com") })
:next(PrintTable)
-- HTML is printed in console...
Returns a Promise
that resolves to the value of the first resolved promise in promises
.
Example:
local snails = {}
for i = 1, 10 do
local d = deferred.new()
timer.Simple(math.random(1, 5), function()
d:resolve(i)
end)
snails[#snails + 1] = d
end
deferred.any(snails):next(function(winner)
print("Winner is snail #"..winner)
end)
-- Winner is snail #5
For each promise p
in the promises
table, as soon as p
resolves to a value x
, fn
is called with x
as the first argument, the index corresponding to p
in promises
as the second argument, and the length of promises
as the third argument. This method returns a Promise
that is resolved to nil
after all the promises have finished. Note that this happens sequentially. If a promise in promises
is the first to be rejected, then the returned promise will reject with the same reason.
This function takes a table of promises and a function filter
and returns a promise that resolves to a table of values that satisfy filter
in order of the given promises
. As the ith promise in promises
resolves to a value x
, filter
is called with x
as the first argument, i as the second argument (the index of the promise), and the length of promises
as the third argument. If filter
returns a truthy value, then x
as added to the table that the returned promise resolves to. If a promise in promises
is the first to be rejected, then the returned promise will reject with the same reason.
Example:
local function fetch(url)
local d = deferred.new()
http.Fetch(url, function(body, size, headers, code)
d:resolve({ url = url, code = code })
end, function(err)
d:reject(err)
end)
return d
end
deferred.filter(
{ fetch("https://google.com"), fetch("https://httpstat.us/404") },
function(res) return res.code ~= 404 end
):next(PrintTable)
-- 1:
-- code = 200
-- url = https://google.com
The promises in promises
are evaluated sequentially and the left-folds the resolved value into a single value using the fn
accumulator function. This method returns a Promise
that resolves to the accumulated value. If a promise in promises
is the first to be rejected, then the returned promise will reject with the same reason. See https://en.wikipedia.org/wiki/Fold_(higher-order_function)
Example:
local snails = {}
for i = 1, 10 do
local d = deferred.new()
local time = math.random(1, 5)
timer.Simple(time, function()
d:resolve(time)
end)
snails[#snails + 1] = d
end
deferred.fold(snails, function(acc, time) return math.max(acc, time) end, 0)
:next(function(longestTime)
print("The slowest snail took "..longestTime.." seconds to finish.")
end)
-- The slowest snail took 5 seconds to finish.
Returns true
if value
is a promise, false
otherwise.
Given a table of values and a function fn
that takes in one of the values as input and returns a promise, this method returns a new promise that resolves to a table where the ith value is the the resolved value of fn(values[i])
. If a promise returned by fn
is the first to be rejected, then the returned promise will reject with the same reason. See https://en.wikipedia.org/wiki/Map_(higher-order_function)
Example:
local urls = {"https://google.com", "https://garrysmod.com"}
local function fetch(url)
local d = deferred.new()
http.Fetch(url, function(body, size, headers, code)
d:resolve(size)
end, function(err)
d:reject(err)
end)
return d
end
deferred.map(urls, fetch):next(PrintTable)
-- 1 = 11384
-- 2 = 23297
Returns a new Promise
object.
Returns a new Promise
object that immediately rejects with reason
as the reason.
Returns a new Promise
object that immediately resolves to value
.
Give a table of promises and a non-negative integer count
, this method returns a promise that resolves to a table of the first count
resolved values in the order that the promises are resolved. If a promise in promises
is the first to be rejected, then the returned promise will reject with the same reason.
Example:
local snails = {}
for i = 1, 10 do
local d = deferred.new()
timer.Simple(math.random(1, 10), function()
d:resolve(i)
end)
snails[#snails + 1] = d
end
deferred.some(snails, 3):next(function(results)
print("First place is snail #"..results[1])
print("Second place is snail #"..results[2])
print("Third place is snail #"..results[3])
end)
-- First place is snail #6
-- Second place is snail #9
-- Third place is snail #5
This is an implementation specific feature where promises that do not have a rejection handler and are rejected will lead to an error in the console. Here is an example:
d = deferred.new()
d:reject("oh no!")
-- Unhandled rejection: oh no!
-- stack traceback:
-- path/to/deferred.lua:187: in function '_handle'
-- path/to/deferred.lua:46: in function 'reject'
-- lua_run:1: in main chunk
Note that the first two lines of the traceback can be ignored. If you wish to disable this feature, add
DEBUG_IGNOREUNHANDLED = true
to your code before using any of the functions.
To test, use lua_openscript path/to/deferred_test.lua
in your console. Or, you can include the deferred_test.lua
file directly.
Inspired by zserge's lua-promises.