Advertisement
tomasoft

Lua Promises

Dec 25th, 2022
1,120
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 10.23 KB | Source Code | 0 0
  1. --=============================================================================
  2. -- Platform specific
  3. --=============================================================================
  4.  
  5. local function doafter(ms, f)
  6.     dolater(ms, f)
  7. end
  8.  
  9. --=============================================================================
  10.  
  11. local function log(...)
  12. --  print('\t\t\t\t[PROMISE]', ...)
  13. end
  14.  
  15. --=============================================================================
  16. -- A simple task queue that is drained on the next turn of the event loop
  17. --=============================================================================
  18.  
  19. local Q = {}
  20.  
  21. local function drain()
  22.     log('Drain', #Q)
  23.     local q = Q
  24.     Q = {}
  25.     for _, f in ipairs(q) do
  26.         f()
  27.     end
  28. end
  29.  
  30. local function queue(f, p)
  31.     assert(f)
  32.     table.insert(Q, function() f(p) end)
  33.     if #Q == 1 then
  34.         doafter(0, drain)
  35.     end
  36. end
  37.  
  38. --=============================================================================
  39.  
  40. local PROMISE_NEW      = 1
  41. local PROMISE_RESOLVED = 2
  42. local PROMISE_REJECTED = 3
  43.  
  44. local PROMISE = {}
  45. PROMISE.__index = PROMISE
  46.  
  47. --=============================================================================
  48.  
  49. local function is_promise(thing)
  50.     return getmetatable(thing) == PROMISE
  51. end
  52.  
  53. --=============================================================================
  54. -- Transferring a value or another promise into the one controlled by handler
  55. --=============================================================================
  56.  
  57. local function become(thing, handler)
  58.     if is_promise(thing) then
  59.         local promise = thing
  60.         if promise:is_resolved() then
  61.             handler:resolve(promise.value)
  62.         elseif promise:is_rejected() then
  63.             handler:reject(promise.value)
  64.         else
  65.             promise:_chain(function(incoming) become(incoming, handler) end)
  66.         end
  67.     else
  68.         handler:resolve(thing)
  69.     end
  70. end
  71.  
  72. --=============================================================================
  73. -- Promise methods
  74. --=============================================================================
  75.  
  76. function PROMISE:is_new()
  77.     return self.state == PROMISE_NEW
  78. end
  79.  
  80. function PROMISE:is_done()
  81.     return self.state ~= PROMISE_NEW
  82. end
  83.  
  84. function PROMISE:is_resolved()
  85.     return self.state == PROMISE_RESOLVED
  86. end
  87.  
  88. function PROMISE:is_rejected()
  89.     return self.state == PROMISE_REJECTED
  90. end
  91.  
  92. -- The then
  93.  
  94. function PROMISE:then_(resolved, rejected)
  95.     local incoming = self
  96.     return Promise(function(handler)
  97.         local function fulfill(promise)
  98.             assert(promise:is_done())
  99.             -- If there is a callback (either resolved or rejected passed in)
  100.             -- we call it
  101.             local callback
  102.             if promise:is_resolved() then
  103.                 callback = resolved
  104.             else
  105.                 callback = rejected
  106.             end
  107.             if callback then
  108.                 local success, new_value = pcall(callback, promise.value)
  109.                 if not success then
  110.                     handler:reject(new_value)
  111.                 else
  112.                     become(new_value, handler)
  113.                 end
  114.             else
  115.                 become(promise, handler)
  116.             end
  117.         end
  118.         if incoming:is_done() then
  119.             queue(fulfill, incoming)
  120.         else
  121.             incoming:_chain(fulfill)
  122.         end
  123.     end)
  124. end
  125.  
  126. PROMISE.and_then = PROMISE.then_
  127.  
  128. -- Executes if the incoming promise was rejected
  129.  
  130. function PROMISE:catch(rejected)
  131.     return self:then_(nil, rejected)
  132. end
  133.  
  134. function PROMISE:fail(rejected)
  135.     return self:then_(nil, rejected)
  136. end
  137.  
  138. -- Executes regardless of whether the incoming promise
  139. -- Calls callback with a state snapshot of the incoming
  140. -- promise and forwards the initial promises state
  141. -- If the callback returns a promise, it will wait until
  142. -- that promise if fulfilled, but will always resolve to
  143. -- the incoming value
  144.  
  145. function PROMISE:finally(callback)
  146.     assert(callback)   
  147.     return self:then_(function(value)
  148.         local result = callback({resolved = true, value = value})
  149.         if is_promise(result) then
  150.             return result:then_resolve(value)
  151.         end
  152.         return value
  153.     end,
  154.     function(value)
  155.         local result = callback({rejected = true, value = value})
  156.         if is_promise(result) then
  157.             return result:then_reject(value)
  158.         end
  159.         error(value, 0)
  160.     end)
  161. end
  162.  
  163. -- If the incoming promise is rejected, throws an error
  164. -- in the next turn of the event loop
  165.  
  166. function PROMISE:done()
  167.     self:then_(nil,function(value)
  168.         queue(error, value)
  169.     end)
  170. end
  171.  
  172. -- Prints out a state snapshot of the incoming promise
  173. -- and forwards its state
  174.  
  175. function PROMISE:print()
  176.     return self:finally(function(snapshot)
  177.         print((snapshot.resolved and 'RESOLVED') or 'REJECTED',
  178.             'value =', snapshot.value)
  179.     end)   
  180. end
  181. -- Resolves to the incoming value after the given delay
  182.  
  183. function PROMISE:delay(ms)
  184.     return self:then_(function(value)
  185.         return Promise(function(handler)           
  186.             doafter(ms, function()
  187.                 handler:resolve(value)
  188.             end)
  189.         end)
  190.     end)
  191. end
  192.  
  193. -- If the incoming promise does not finish within the given
  194. -- amount of time, this will reject it with value.
  195. -- If the incoming is already done, nothing happens.
  196.  
  197. function PROMISE:timeout(ms, value)
  198.     local incoming = self
  199.     if not incoming:is_done() then
  200.         doafter(ms, function() incoming:_reject(value) end)
  201.     end
  202.     return incoming
  203. end
  204.  
  205. -- Resolves immediately to the given value
  206.  
  207. function PROMISE:then_resolve(value)
  208.     return self:then_(function()
  209.         return value
  210.     end)
  211. end
  212.  
  213. -- Rejects immediately with the given value
  214.  
  215. function PROMISE:then_reject(value)
  216.     return self:then_(function()
  217.         error(value, 0)
  218.     end,
  219.     function()
  220.         error(value, 0)
  221.     end)
  222. end
  223.  
  224. -- Expects an array of promises. Waits until they
  225. -- are ALL resolved or ONE is rejected. If they are
  226. -- all resolved, resolves to an array of their values
  227. -- in the same order. If any one is rejected, rejects
  228. -- with that value
  229.  
  230. function PROMISE:all()
  231.     return self:then_(function(promises)
  232.         return Promise(function(handler)
  233.             local count = #promises
  234.             local results = {}
  235.             if count == 0 then
  236.                 handler:resolve(results)
  237.             else
  238.                 for i, promise in ipairs(promises) do
  239.                     promise:then_(function(value)
  240.                         results[i] = value
  241.                         count = count - 1
  242.                         if count == 0 then
  243.                             handler:resolve(results)
  244.                         end
  245.                     end,
  246.                     function(value)
  247.                         handler:reject(value)
  248.                     end)
  249.                 end
  250.             end
  251.         end)
  252.     end)
  253. end
  254.  
  255. -- Expects an array of promises. Waits until they are
  256. -- ALL resolved OR rejected. Resolves to an array of
  257. -- state snapshots, each one containing the value and
  258. -- a boolean 'resolved' if it was resolved or 'rejected'
  259. -- if it failed
  260.  
  261. function PROMISE:all_settled()
  262.     return self:then_(function(promises)
  263.         return Promise(function(handler)
  264.             local count = #promises
  265.             local results = {}
  266.             if count == 0 then
  267.                 handler:resolve(results)
  268.             else
  269.                 local function fulfill(i, value)
  270.                     results[i] = value
  271.                     count = count - 1
  272.                     log('Finished promise',i,'with',value,'have',count,'left')
  273.                     if count == 0 then
  274.                         handler:resolve(results)
  275.                     end
  276.                 end
  277.                 for i, promise in ipairs(promises) do
  278.                     promise:then_(function(value)
  279.                         fulfill(i, {resolved = true, value = value})
  280.                     end,
  281.                     function(value)
  282.                         fulfill(i, {rejected = true, value = value})
  283.                     end)
  284.                 end
  285.             end
  286.         end)
  287.     end)
  288. end
  289.  
  290. --=============================================================================
  291. -- Private-ish
  292. --=============================================================================
  293.  
  294. -- Adds a function to this promise that will be called when _invoke_chain is
  295. -- called (which happens automatically when a promise goes from pending to
  296. -- resolved or rejected). You can only do this when a promise is new/pending
  297. -- The callback receives the promise as its first and only argument
  298.  
  299. function PROMISE:_chain(callback)
  300.     assert(callback)
  301.     assert(self:is_new())
  302.     local chain = self.chain
  303.     if not chain then
  304.         chain = {}
  305.         self.chain = chain
  306.     end
  307.     table.insert(chain, callback)
  308. end
  309.  
  310. -- Calls all the callbacks that have been added to this promise with _chain
  311. -- You can only do this when the promise is resolved or rejected
  312.  
  313. function PROMISE:_invoke_chain()
  314.     assert(self:is_done())
  315.     local chain = self.chain
  316.     if chain then
  317.         self.chain = nil
  318.         for _, callback in ipairs(chain) do
  319.             callback(self)
  320.         end
  321.     end
  322. end
  323.  
  324. -- Switches state to resolved, takes on the given value and calls all the
  325. -- callbacks in the chain. Can only call it once.
  326.  
  327. function PROMISE:_resolve(value)       
  328.     if self:is_new() then
  329.         assert(not is_promise(value))
  330.         self.state = PROMISE_RESOLVED
  331.         self.value = value
  332.         self:_invoke_chain()
  333.     end
  334. end
  335.  
  336. -- Switches state to rejected, takes on the given value and calls all the
  337. -- callbacks in the chain. Can only call it once.
  338.  
  339. function PROMISE:_reject(value)
  340.     if self:is_new() then
  341.         assert(not is_promise(value))
  342.         self.state = PROMISE_REJECTED
  343.         self.value = value
  344.         self:_invoke_chain()
  345.     end
  346. end
  347.  
  348. --=============================================================================
  349. -- Resolver
  350. --=============================================================================
  351.  
  352. local RESOLVER = {}
  353. RESOLVER.__index = RESOLVER;
  354.  
  355. function RESOLVER:resolve(value)
  356.     self._promise:_resolve(value)
  357. end
  358.  
  359. function RESOLVER:reject(value)
  360.     self._promise:_reject(value)
  361. end
  362.  
  363. local function Resolver(promise)
  364.     return setmetatable({_promise = promise}, RESOLVER)
  365. end
  366.  
  367. --=============================================================================
  368. -- Promise constructor
  369. --=============================================================================
  370. -- If you pass a function, that function will be called synchronously with the
  371. -- resolver for the new promise. If the function throws an error, the new
  372. -- promise will be rejected immediately.
  373. --
  374. -- If you pass a value that is not a promise, the new promise will be resolved
  375. -- with that value immediately.
  376. --
  377. -- If you pass another promise, the new one will assume the other's value -
  378. -- either immediately if it is resolved or rejected, or later if it is not.
  379.  
  380. local N = 1
  381.  
  382. function Promise(handler)
  383.  
  384.     log('Created promise', N)
  385.     N = N +1
  386.  
  387.     local promise = setmetatable({state = PROMISE_NEW}, PROMISE)
  388.     local resolver = Resolver(promise)
  389.  
  390.     if type(handler) ~= 'function' then
  391.         become(handler, resolver)
  392.     else
  393.         local success, value = pcall(handler, resolver)
  394.         if not success then
  395.             resolver:reject(value)
  396.         end
  397.     end
  398.     return promise
  399. end
  400.  
  401. function PromiseThen(resolved, rejected)
  402.     return Promise():and_then(resolved, rejected)
  403. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement