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
Promiseobject 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 #5For 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.comThe 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 = 23297Returns 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 #5This 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 chunkNote that the first two lines of the traceback can be ignored. If you wish to disable this feature, add
DEBUG_IGNOREUNHANDLED = trueto 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.