From 2b0792b1167079a2842dc09859d0c17ee25e9b6b Mon Sep 17 00:00:00 2001 From: s1n7ax Date: Sun, 26 Nov 2023 21:45:40 +0530 Subject: [PATCH] refactor!: go from promises to co-routines --- lua/java-core/adapters/init.lua | 2 +- lua/java-core/api/test.lua | 76 +--- lua/java-core/dap/init.lua | 64 ++- .../ls/clients/java-debug-client.lua | 12 +- lua/java-core/ls/clients/java-test-client.lua | 44 +- lua/java-core/ls/clients/jdtls-client.lua | 38 +- lua/java-core/ls/servers/jdtls/init.lua | 8 +- lua/java-core/utils/async.lua | 130 ++++++ lua/java-core/utils/promise.lua | 428 ------------------ 9 files changed, 231 insertions(+), 571 deletions(-) create mode 100644 lua/java-core/utils/async.lua delete mode 100644 lua/java-core/utils/promise.lua diff --git a/lua/java-core/adapters/init.lua b/lua/java-core/adapters/init.lua index 7d3d6fa..279ce87 100644 --- a/lua/java-core/adapters/init.lua +++ b/lua/java-core/adapters/init.lua @@ -37,7 +37,7 @@ end ---Ruterns the launch argument parameters for given test or tests ---@param tests JavaCoreTestDetails | JavaCoreTestDetails[] ----@return JavaCoreTestResolveJUnitLaunchArgumentsParams +---@return JavaCoreTestResolveJUnitLaunchArgumentsParams # junit launch arguments function M.get_junit_launch_argument_params(tests) if not vim.tbl_islist(tests) then return { diff --git a/lua/java-core/api/test.lua b/lua/java-core/api/test.lua index 111c75b..b5c19cd 100644 --- a/lua/java-core/api/test.lua +++ b/lua/java-core/api/test.lua @@ -1,7 +1,6 @@ local log = require('java-core.utils.log') local data_adapters = require('java-core.adapters') -local Promise = require('java-core.utils.promise') local TestReport = require('java-core.dap.test-report') local JavaDapRunner = require('java-core.dap.runner') local JavaDebug = require('java-core.ls.clients.java-debug-client') @@ -43,74 +42,47 @@ end ---Runs the test class in the given buffer ---@param buffer integer ---@param config JavaCoreDapLauncherConfigOverridable ----@return Promise function M:run_class_by_buffer(buffer, config) - return self:get_test_class_by_buffer(buffer):thenCall(function(tests) - return self:run_test(tests, config) - end) + local tests = self:get_test_class_by_buffer(buffer) + self:run_test(tests, config) end ---Returns test classes in the given buffer ---@priate ---@param buffer integer ----@return Promise # Promise +---@return JavaCoreTestDetailsWithChildrenAndRange # get test class details function M:get_test_class_by_buffer(buffer) log.debug('finding test class by buffer') - return Promise.resolve():thenCall(function() - if not vim.api.nvim_buf_is_valid(buffer) then - local msg = 'passed buffer' .. tostring(buffer) .. ' is not valid' - log.error(msg) - error(msg) - end - - local uri = vim.uri_from_bufnr(buffer) - return self.test_client:find_test_types_and_methods(uri) - end) + local uri = vim.uri_from_bufnr(buffer) + return self.test_client:find_test_types_and_methods(uri) end ---Run the given test ---@private ---@param tests JavaCoreTestDetails[] ---@param config? JavaCoreDapLauncherConfigOverridable config to override the default values in test launcher config ----@return Promise # function M:run_test(tests, config) ---@type JavaCoreTestJunitLaunchArguments - local launch_args - - return self.test_client - :resolve_junit_launch_arguments( - data_adapters.get_junit_launch_argument_params(tests) - ) - :thenCall( - ---@param _launch_args JavaCoreTestJunitLaunchArguments - function(_launch_args) - launch_args = _launch_args - - return self.debug_client:resolve_java_executable( - launch_args.mainClass, - launch_args.projectName - ) - end - ) - :thenCall( - ---@param java_exec string - function(java_exec) - local dap_launcher_config = - --@TODO I don't know why the hell debug is hard coded here - data_adapters.get_dap_launcher_config(launch_args, java_exec, { - debug = true, - label = 'Launch All Java Tests', - }) - - log.debug('dap launcher config is: ', dap_launcher_config) - - dap_launcher_config = - vim.tbl_deep_extend('force', dap_launcher_config, config or {}) - - return self.runner:run_by_config(dap_launcher_config) - end - ) + local launch_args = self.test_client:resolve_junit_launch_arguments( + data_adapters.get_junit_launch_argument_params(tests) + ) + + local java_exec = self.debug_client:resolve_java_executable( + launch_args.mainClass, + launch_args.projectName + ) + + local dap_launcher_config = + data_adapters.get_dap_launcher_config(launch_args, java_exec, { + debug = true, + label = 'Launch All Java Tests', + }) + + dap_launcher_config = + vim.tbl_deep_extend('force', dap_launcher_config, config or {}) + + self.runner:run_by_config(dap_launcher_config) end return M diff --git a/lua/java-core/dap/init.lua b/lua/java-core/dap/init.lua index 2f038bd..0d91bb6 100644 --- a/lua/java-core/dap/init.lua +++ b/lua/java-core/dap/init.lua @@ -1,8 +1,6 @@ local log = require('java-core.utils.log') local adapters = require('java-core.dap.adapters') -local List = require('java-core.utils.list') local JavaDebug = require('java-core.ls.clients.java-debug-client') -local Promise = require('java-core.utils.promise') ---@class JavaCoreDap ---@field client LspClient @@ -32,55 +30,45 @@ end ---@field port integer ---Returns the dap adapter config ----@return Promise # Promise +---@return JavaCoreDapAdapter # dap adapter details function M:get_dap_adapter() - log.info('creating dap adapter for java') - - return self.java_debug:start_debug_session():thenCall( - ---@param port integer - function(port) - return { - type = 'server', - host = '127.0.0.1', - port = port, - } - end - ) + log.debug('creating dap adapter for java') + + local port = self.java_debug:start_debug_session() + + return { + type = 'server', + host = '127.0.0.1', + port = port, + } end ---Returns the dap configuration for the current project ----@return Promise # Promise +---@return JavaCoreDapLauncherConfig[] # dap configuration details function M:get_dap_config() log.info('creating dap configuration for java') - return self.java_debug:resolve_main_class():thenCall( - ---@param mains JavaDebugResolveMainClassRecord[] - function(mains) - local config_promises = List:new(mains):map(function(main) - return self:get_dap_config_record(main) - end) + local mains = self.java_debug:resolve_main_class() + local config = {} - return Promise.all(config_promises) - end - ) + for _, main in ipairs(mains) do + table.insert(config, self:get_dap_config_record(main)) + end + + return config end ---Returns the dap config for the given main class ---@param main JavaDebugResolveMainClassRecord ----@return Promise # Promise +---@return JavaCoreDapLauncherConfig # dap launch configuration record function M:get_dap_config_record(main) - return Promise.all({ - self.java_debug:resolve_classpath(main.mainClass, main.projectName), - self.java_debug:resolve_java_executable(main.mainClass, main.projectName), - }):thenCall(function(res) - ---@type string[][] - local classpaths = res[1] - - ---@type string - local java_exec = res[2] - - return adapters.get_dap_config(main, classpaths, java_exec) - end) + local classpaths = + self.java_debug:resolve_classpath(main.mainClass, main.projectName) + + local java_exec = + self.java_debug:resolve_java_executable(main.mainClass, main.projectName) + + return adapters.get_dap_config(main, classpaths, java_exec) end return M diff --git a/lua/java-core/ls/clients/java-debug-client.lua b/lua/java-core/ls/clients/java-debug-client.lua index 5baef2a..ab94dbe 100644 --- a/lua/java-core/ls/clients/java-debug-client.lua +++ b/lua/java-core/ls/clients/java-debug-client.lua @@ -9,7 +9,7 @@ local M = JdtlsClient:new() ---@field fileName string ---Returns a list of main classes in the current workspace ----@return Promise # Promise +---@return JavaDebugResolveMainClassRecord[] # resolved main class function M:resolve_main_class() return self:execute_command('vscode.java.resolveMainClass') end @@ -17,7 +17,7 @@ end ---Returns module paths and class paths of a given main class ---@param project_name string ---@param main_class string ----@return Promise # Promise +---@return string[][] # resolved class and module paths function M:resolve_classpath(main_class, project_name) return self:execute_command( 'vscode.java.resolveClasspath', @@ -28,7 +28,7 @@ end ---Returns the path to java executable for a given main class ---@param project_name string ---@param main_class string ----@return Promise # Promise +---@return string # path to java executable function M:resolve_java_executable(main_class, project_name) return self:execute_command('vscode.java.resolveJavaExecutable', { main_class, @@ -41,7 +41,7 @@ end ---@param main_class string ---@param inheritedOptions boolean ---@param expectedOptions { [string]: any } ----@return Promise # Promise +---@return boolean # true if the setting is the expected setting function M:check_project_settings( main_class, project_name, @@ -60,7 +60,7 @@ function M:check_project_settings( end ---Starts a debug session and returns the port number ----@return Promise # Promise +---@return integer # port number of the debug session function M:start_debug_session() return self:execute_command('vscode.java.startDebugSession') end @@ -78,7 +78,7 @@ M.CompileWorkspaceStatus = { ---@param project_name? string ---@param file_path? string ---@param is_full_build boolean ----@return Promise # Promise +---@return CompileWorkspaceStatus # compiled status function M:build_workspace(main_class, project_name, file_path, is_full_build) return self:execute_command( 'vscode.java.buildWorkspace', diff --git a/lua/java-core/ls/clients/java-test-client.lua b/lua/java-core/ls/clients/java-test-client.lua index 4bba924..785fdf1 100644 --- a/lua/java-core/ls/clients/java-test-client.lua +++ b/lua/java-core/ls/clients/java-test-client.lua @@ -33,7 +33,7 @@ local JdtlsClient = require('java-core.ls.clients.jdtls-client') local M = JdtlsClient:new() ---Returns a list of project details in the current root ----@return Promise # Promise +---@return JavaCoreTestDetails[] # test details of the projects function M:find_java_projects() return self:execute_command( 'vscode.java.test.findJavaProjects', @@ -44,7 +44,7 @@ end ---Returns a list of test package details ---@param handler string ---@param token? string ----@return Promise # Promise +---@return JavaCoreTestDetailsWithChildren[] # test package details function M:find_test_packages_and_types(handler, token) return self:execute_command( 'vscode.java.test.findTestPackagesAndTypes', @@ -55,7 +55,7 @@ end ---Returns test informations in a given file ---@param file_uri string ---@param token? string ----@return Promise # Promise +---@return JavaCoreTestDetailsWithChildrenAndRange[] # test details function M:find_test_types_and_methods(file_uri, token) return self:execute_command( 'vscode.java.test.findTestTypesAndMethods', @@ -80,31 +80,21 @@ end ---Returns junit launch arguments ---@param args JavaCoreTestResolveJUnitLaunchArgumentsParams ----@return Promise # Promise +---@return JavaCoreTestJunitLaunchArguments # junit launch arguments function M:resolve_junit_launch_arguments(args) - return self - :execute_command( - 'vscode.java.test.junit.argument', - vim.fn.json_encode(args) - ) - :thenCall( - - ---@class JavaCoreTestJunitLaunchArgumentsResponse - ---@field body JavaCoreTestJunitLaunchArguments - ---@field status integer - - ---@param res JavaCoreTestJunitLaunchArgumentsResponse - function(res) - if not res.body then - local msg = 'Failed to retrive JUnit launch arguments' - - log.error(msg, res) - error(msg) - end - - return res.body - end - ) + local launch_args = self:execute_command( + 'vscode.java.test.junit.argument', + vim.fn.json_encode(args) + ) + + if not launch_args.body then + local msg = 'Failed to retrive JUnit launch arguments' + + log.error(msg, launch_args) + error(msg) + end + + return launch_args.body end ---@enum JavaCoreTestKind diff --git a/lua/java-core/ls/clients/jdtls-client.lua b/lua/java-core/ls/clients/jdtls-client.lua index fb807e4..7701035 100644 --- a/lua/java-core/ls/clients/jdtls-client.lua +++ b/lua/java-core/ls/clients/jdtls-client.lua @@ -1,5 +1,6 @@ local log = require('java-core.utils.log') -local Promise = require('java-core.utils.promise') +local async = require('java-core.utils.async') +local await = async.wait_handle_error ---@class JavaCoreJdtlsClient ---@field client LspClient @@ -21,31 +22,38 @@ end ---@param command string ---@param arguments? string | string[] ---@param buffer? integer ----@return Promise # Promise +---@return any function M:execute_command(command, arguments, buffer) - return Promise.new(function(resolve, reject) - local cmd_info = { - command = command, - arguments = arguments, - } + log.debug('executing: workspace/executeCommand - ' .. command) - log.debug('executing: workspace/executeCommand - ' .. command) + local cmd_info = { + command = command, + arguments = arguments, + } - self.client.request('workspace/executeCommand', cmd_info, function(err, res) + return await(function(callback) + local on_response = function(err, result) if err then log.error(command .. ' failed! arguments: ', arguments, ' error: ', err) - reject(err) else - log.debug(command .. ' success! response: ', res) - resolve(res) + log.debug(command .. ' success! response: ', result) end - end, buffer) + + callback(err, result) + end + + return self.client.request( + 'workspace/executeCommand', + cmd_info, + on_response, + buffer + ) end) end ---Returns the decompiled class file content ---@param uri string uri of the class file ----@return Promise # Promise - decompiled file content +---@return string # decompiled file content function M:java_decompile(uri) return self:execute_command('java.decompile', { uri }) end @@ -67,7 +75,7 @@ end ---Returns true if the LS supports the given command ---@param command_name string name of the command ----@return boolean +---@return boolean # true if the command is supported function M:has_command(command_name) local commands = self:get_capability('executeCommandProvider', 'commands') diff --git a/lua/java-core/ls/servers/jdtls/init.lua b/lua/java-core/ls/servers/jdtls/init.lua index f957c81..e4bca6b 100644 --- a/lua/java-core/ls/servers/jdtls/init.lua +++ b/lua/java-core/ls/servers/jdtls/init.lua @@ -1,10 +1,10 @@ -local util = require('lspconfig.util') -local path = require('java-core.utils.path') +local config = require('java-core.ls.servers.jdtls.config') +local log = require('java-core.utils.log') local mason = require('java-core.utils.mason') +local path = require('java-core.utils.path') local plugins = require('java-core.ls.servers.jdtls.plugins') -local log = require('java-core.utils.log') +local util = require('lspconfig.util') local workspace = require('java-core.ls.servers.jdtls.workspace') -local config = require('java-core.ls.servers.jdtls.config') local M = {} diff --git a/lua/java-core/utils/async.lua b/lua/java-core/utils/async.lua new file mode 100644 index 0000000..5d67fff --- /dev/null +++ b/lua/java-core/utils/async.lua @@ -0,0 +1,130 @@ +local co = coroutine + +local wrap = function(func) + assert(type(func) == 'function', 'type error :: expected func') + local factory = function(...) + local params = { ... } + local thunk = function(step) + table.insert(params, step) + return func(unpack(params)) + end + return thunk + end + return factory +end + +local function async(func) + local error_handler = nil + + local async_thunk_factory = wrap(function(handler, parent_handler_callback) + assert(type(handler) == 'function', 'type error :: expected func') + local thread = co.create(handler) + local step = nil + + step = function(...) + local ok, thunk = co.resume(thread, ...) + + -- when an error() is thrown after co-routine is resumed, obviously further + -- processing stops, and resume returns ok(false) and thunk(error) returns + -- the error message + if not ok then + if error_handler then + error_handler(thunk) + return + end + + if parent_handler_callback then + parent_handler_callback(thunk) + return + end + + error('unhandled error ' .. thunk) + end + + assert(ok, thunk) + if co.status(thread) == 'dead' then + if parent_handler_callback then + parent_handler_callback(thunk) + end + else + thunk(step) + end + end + + step() + end) + + return setmetatable({}, { + __call = function(_, ...) + return async_thunk_factory(func)(...) + end, + __index = function(this, key) + if key == 'catch' then + return function(_error_handler) + error_handler = _error_handler + return this + end + end + + if key == 'run' then + return function(...) + return async_thunk_factory(func)(...) + end + end + + error('cannot index ' .. key .. ' in async function') + end, + }) +end + +-- many thunks -> single thunk +local join = function(thunks) + local len = #thunks + local done = 0 + local acc = {} + + local thunk = function(step) + if len == 0 then + return step() + end + for i, tk in ipairs(thunks) do + assert(type(tk) == 'function', 'thunk must be function') + local callback = function(...) + acc[i] = { ... } + done = done + 1 + if done == len then + step(unpack(acc)) + end + end + tk(callback) + end + end + return thunk +end + +local await = function(defer) + return co.yield(defer) +end + +local await_handle_error = function(defer) + local err, value = co.yield(defer) + + if err then + error(err) + end + + return value +end + +local await_all = function(defer) + assert(type(defer) == 'table', 'type error :: expected table') + return co.yield(join(defer)) +end + +return { + sync = async, + wait_handle_error = await_handle_error, + wait = await, + wait_all = await_all, + wrap = wrap, +} diff --git a/lua/java-core/utils/promise.lua b/lua/java-core/utils/promise.lua deleted file mode 100644 index bdf0e89..0000000 --- a/lua/java-core/utils/promise.lua +++ /dev/null @@ -1,428 +0,0 @@ ---[[ ---FROM: https://github.com/pyericz/promise-lua ---]] - ---[[ -MIT License - -Copyright (c) 2019 Eric Zhang - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ---]] - -local PENDING = 'PENDING' -local FULFILLED = 'FULFILLED' -local REJECTED = 'REJECTED' - -local function noop() end - -local function isCallable(x) - return type(x) == 'function' - or not not (getmetatable(x) and getmetatable(x).__call) -end - -local function isThenable(x) - return type(x) == 'table' and x.thenCall ~= nil -end - -local promise = {} - -function promise.__tostring(p) - local __tostring = promise.__tostring - promise.__tostring = nil - local str = string.format('Promise: %s', tostring(p):sub(8)) - promise.__tostring = __tostring - return str -end - -local newPromise -local resolve -local promiseOnFulfilled -local promiseOnRejected - -local function isPromise(x) - if type(x) ~= 'table' then - return false - end - local mtSeen = {} - local mt = getmetatable(x) - while mt ~= nil and not mtSeen[mt] do - if mt == promise then - return true - end - mtSeen[mt] = true - mt = getmetatable(mt) - end - return false -end - -local function execFulfilled(thenInfo, value) - local n = thenInfo - if not isCallable(n.onFulfilled) then - promiseOnFulfilled(n.promise, value) - else - local success, ret = pcall(n.onFulfilled, value) - if success then - resolve(n.promise, ret) - else - promiseOnRejected(n.promise, ret) - end - end -end - -local function execRejected(thenInfo, reason) - local n = thenInfo - if not isCallable(n.onRejected) then - promiseOnRejected(n.promise, reason) - else - local success, ret = pcall(n.onRejected, reason) - if success then - resolve(n.promise, ret) - else - promiseOnRejected(n.promise, ret) - end - end -end - -promiseOnFulfilled = function(p, value) - if p._state == PENDING then - p._value = value - p._reason = nil - p._state = FULFILLED - end - for _, n in ipairs(p.thenInfoList) do - execFulfilled(n, value) - end -end - -promiseOnRejected = function(p, reason) - if p._state == PENDING then - p._value = nil - p._reason = reason - p._state = REJECTED - end - for _, n in ipairs(p.thenInfoList) do - execRejected(n, reason) - end -end - -local function resolveThenable(p, x) - local thenCall = x.thenCall - if isCallable(thenCall) then - local isCalled = false - local function resolvePromise(y) - if isCalled then - return - end - isCalled = true - resolve(p, y) - end - local function rejectPromise(r) - if isCalled then - return - end - isCalled = true - promiseOnRejected(p, r) - end - local success, err = pcall(thenCall, x, resolvePromise, rejectPromise) - if not success then - if not isCalled then - promiseOnRejected(p, err) - end - end - else - promiseOnFulfilled(p, x) - end -end - ---[[ - define promise resolution procedure ---]] -resolve = function(p, x) - if p == x then - promiseOnRejected( - p, - 'TypeError: Promise resolution procedure got two identical parameters.' - ) - return - end - if isPromise(x) then - if x._state == PENDING then - p._state = PENDING - end - resolveThenable(p, x) - elseif isThenable(x) then - resolveThenable(p, x) - else - promiseOnFulfilled(p, x) - end -end - -function promise:new() - local p = {} - setmetatable(p, self) - self.__index = self - p.thenInfoList = {} - p._state = PENDING - p._value = nil - p._reason = nil - - return p -end - -function promise:thenCall(onFulfilled, onRejected) - local p = newPromise(noop) - - local thenInfo = { - promise = p, - } - - if isCallable(onFulfilled) then - thenInfo.onFulfilled = onFulfilled - end - if isCallable(onRejected) then - thenInfo.onRejected = onRejected - end - - if self._state == FULFILLED then - execFulfilled(thenInfo, self._value) - elseif self._state == REJECTED then - execRejected(thenInfo, self._reason) - end - - table.insert(self.thenInfoList, thenInfo) - - return p -end - -function promise:catch(onRejected) - return self:thenCall(nil, onRejected) -end - -newPromise = function(func) - local obj = promise:new() - local isCalled = false - local function onFulfilled(value) - if isCalled then - return - end - isCalled = true - promiseOnFulfilled(obj, value) - end - - local function onRejected(reason) - if isCalled then - return - end - isCalled = true - promiseOnRejected(obj, reason) - end - - if isCallable(func) then - func(onFulfilled, onRejected) - end - return obj -end - -local Promise = {} -setmetatable(Promise, { - __call = function(_, func) - return newPromise(func) - end, -}) - -Promise.new = newPromise - -local function newPromiseFromValue(value) - local p = newPromise(noop) - p._state = FULFILLED - p._value = value - p._reason = nil - return p -end - -function Promise.resolve(value) - if isPromise(value) then - return value - end - if isThenable(value) then - local thenCall = value.thenCall - if isCallable(thenCall) then - return newPromise(function(onFulfilled, onRejected) - value:thenCall(onFulfilled, onRejected) - end) - else - return newPromise(function(_, onRejected) - onRejected( - string.format( - 'TypeError: thenCall must be a function (a %s value)', - type(thenCall) - ) - ) - end) - end - end - return newPromiseFromValue(value) -end - -function Promise.reject(value) - return newPromise(function(_, onRejected) - onRejected(value) - end) -end - -function promise:finally(func) - return self:thenCall(function(value) - return Promise.resolve(func()):thenCall(function() - return Promise.resolve(value) - end) - end, function(reason) - return Promise.resolve(func()):thenCall(function() - return Promise.reject(reason) - end) - end) -end - -function Promise.race(values) - assert( - type(values) == 'table', - string.format('Promise.race needs an table (a %s value)', type(values)) - ) - assert(next(values) ~= nil, 'No candidates available for racing.') - return newPromise(function(onFulfilled, onRejected) - for _, value in pairs(values) do - Promise.resolve(value):thenCall(onFulfilled, onRejected) - end - end) -end - -function Promise.all(array) - assert( - type(array) == 'table', - string.format('Promise.all needs an array table (a %s value)', type(array)) - ) - local args = {} - for i = 1, #array do - args[i] = array[i] - end - - return newPromise(function(onFulfilled, onRejected) - if #args == 0 then - return onFulfilled({}) - end - local remaining = #args - local function res(i, val) - if isPromise(val) then - if val._state == FULFILLED then - return res(i, val._value) - end - if val._state == REJECTED then - onRejected(val._reason) - end - val:thenCall(function(v) - res(i, v) - end, onRejected) - return - elseif isThenable(val) then - local thenCall = val.thenCall - if isCallable(thenCall) then - local p = newPromise(function(r, rj) - val:thenCall(r, rj) - end) - p:thenCall(function(v) - res(i, v) - end, onRejected) - return - end - end - args[i] = val - remaining = remaining - 1 - if remaining == 0 then - onFulfilled(args) - end - end - for i = 1, #args do - res(i, args[i]) - end - end) -end - -function Promise.serial(array) - assert( - type(array) == 'table', - string.format( - 'Promise.serial needs an array table (a %s value)', - type(array) - ) - ) - local args = {} - for i = 1, #array do - args[i] = array[i] - end - - return newPromise(function(onFulfilled, onRejected) - if #args == 0 then - return onFulfilled({}) - end - local remaining = #args - local function res(i, val) - if isPromise(val) then - if val._state == FULFILLED then - return res(i, val._value) - end - if val._state == REJECTED then - onRejected(val._reason) - end - val:thenCall(function(v) - res(i, v) - end, onRejected) - return - elseif isThenable(val) then - local thenCall = val.thenCall - if isCallable(thenCall) then - local p = newPromise(function(r, rj) - val:thenCall(r, rj) - end) - p:thenCall(function(v) - res(i, v) - end, onRejected) - return - end - end - args[i] = val - remaining = remaining - 1 - if remaining == 0 then - onFulfilled(args) - else - if isCallable(args[i + 1]) then - res(i + 1, newPromise(args[i + 1])) - else - res(i + 1, args[i + 1]) - end - end - end - if isCallable(args[1]) then - res(1, newPromise(args[1])) - else - res(1, args[1]) - end - end) -end - -return Promise