Last active
November 5, 2021 06:26
-
-
Save starwing/0a475c45671e760206f709012fe20549 to your computer and use it in GitHub Desktop.
Promise for Lua
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local assert = assert | |
local type = type | |
local getmetatable = getmetatable | |
local setmetatable = setmetatable | |
local xpcall = xpcall | |
local debug_traceback = debug.traceback | |
local Promise = {} do | |
local Fulfilled = "Fulfilled" | |
local Rejected = "Rejected" | |
local Thenable = {} | |
Thenable.__name = "Promise" | |
Thenable.__index = Thenable | |
local function execute_handlers(self) | |
assert(self.state) | |
local value, err = self.value | |
if self.state == Rejected then | |
value, err = nil, value | |
end | |
local i = 1 | |
while i <= #self do | |
self[i](value, err) | |
i = i + 1 | |
end | |
for i = 1, #self do | |
self[i] = nil | |
end | |
end | |
local function transmit(self, state, value) | |
if self.state or not state or value == self then | |
return | |
end | |
local subscribe = type(value) == "table" and value.subscribe | |
if not subscribe then | |
local mt = getmetatable(value) | |
local index = mt and mt.__index | |
subscribe = index and index.subscribe | |
end | |
if subscribe then | |
local function next_handler(value, err) | |
if err then | |
transmit(self, Rejected, err) | |
else | |
transmit(self, Fulfilled, value) | |
end | |
end | |
return subscribe(value, next_handler) | |
end | |
self.state = state | |
self.value = value | |
return execute_handlers(self) | |
end | |
local function new(f) | |
local self = setmetatable({state = nil, value = nil}, Thenable) | |
local ok, v = xpcall(f, debug_traceback, self) | |
if not ok then | |
transmit(self, Rejected, v) | |
elseif v then | |
transmit(self, Fulfilled, v) | |
end | |
return self | |
end | |
function Promise.new(f) | |
return new(f) | |
end | |
function Promise.resolve(value) | |
return new(function(p) return p:resolve(value) end) | |
end | |
function Promise.reject(err) | |
return new(function(p) return p:reject(err) end) | |
end | |
function Thenable:__tostring() | |
return ("Promise: %p { state = %s, value = %s }"):format( | |
self, self.state or "Pending", self.value) | |
end | |
function Thenable:subscribe(h) | |
if not self.state then | |
self[#self+1] = h | |
else | |
local value, err = self.value | |
if self.state == Rejected then | |
value, err = nil, value | |
end | |
h(value, err) | |
end | |
end | |
function Thenable:next(fulfilled, rejected) | |
return new(function(p) | |
local function next_handler(value, err) | |
if err then | |
if rejected then | |
local ok, v = xpcall(rejected, debug_traceback, err) | |
if ok and v then return p:resolve(v) end | |
err = v or err | |
end | |
return p:reject(err) | |
else | |
if fulfilled then | |
local ok, v = xpcall(fulfilled, debug_traceback, value) | |
if not ok then return p:reject(v) end | |
value = v or value | |
end | |
return p:resolve(value) | |
end | |
end | |
return self:subscribe(next_handler) | |
end) | |
end | |
function Thenable:catch(onRejected) | |
return self:next(nil, onRejected) | |
end | |
function Thenable:resolve(value) | |
return transmit(self, Fulfilled, value) | |
end | |
function Thenable:reject(err) | |
return transmit(self, Rejected, err) | |
end | |
end | |
local function test() | |
local cnt = 0 | |
Promise.reject(1): | |
catch(function(err) | |
assert(err == 1) | |
cnt = cnt + 1 | |
return 2 | |
end): | |
next(function(v) | |
assert(v == 2) | |
cnt = cnt + 1 | |
end) | |
assert(cnt == 2) | |
cnt = 0 | |
Promise.resolve(1): | |
next(function(v) | |
assert(v == 1) | |
cnt = cnt + 1 | |
return Promise.resolve(v + 1) | |
end): | |
next(function(v) | |
assert(v == 2) | |
cnt = cnt + 1 | |
return Promise.resolve(v + 2) | |
end): | |
next(function(v) | |
assert(v == 4) | |
cnt = cnt + 1 | |
end): | |
next(function(v) | |
assert(v == 4) | |
cnt = cnt + 1 | |
end): | |
next(function(v) | |
assert(v == 4) | |
cnt = cnt + 1 | |
error("foo") | |
end): | |
catch(function(err) | |
assert(err:match "foo") | |
cnt = cnt + 1 | |
end) | |
assert(cnt == 6) | |
cnt = 0 | |
Promise.resolve(1) | |
:next(nil) | |
:next(nil) | |
:next(nil) | |
:next(nil) | |
:next(function(v) | |
assert(v == 1) | |
cnt = cnt + 1 | |
end) | |
assert(cnt == 1) | |
cnt = 0 | |
local done | |
Promise.new(function(p) | |
cnt = cnt + 1 | |
done = function() | |
p:resolve(1) | |
end | |
end): | |
next(function(v) | |
assert(v == 1) | |
cnt = cnt + 1 | |
end) | |
assert(cnt == 1) | |
done() | |
assert(cnt == 2) | |
end | |
test() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment