lua-async-await icon indicating copy to clipboard operation
lua-async-await copied to clipboard

Async functions with arguments do not work (parameters are ignored)

Open luiz00martins opened this issue 2 years ago • 5 comments
trafficstars

Simple example:

local do_thing = a.sync(function (val)
  local o = 10
  return o + val
end)

print(do_thing(5))

This should print 15, but instead it results in an error at line 3: attempt to perform arithmetic on a nil value (local 'val').

It seems like the library didn't pass the value 5 to the function.

luiz00martins avatar Dec 19 '22 13:12 luiz00martins

Even in the preview from the README, it's not clear to me how parameters should work. For example:

local a = require "async"

local do_thing = a.sync(function (val)
  local o = a.wait(async_func())
  return o + val
end)

local main = a.sync(function ()
  local thing = a.wait(do_thing()) -- composable!

  local x = a.wait(async_func())
  local y, z = a.wait_all{async_func(), async_func()}
end)

main()

The function do_thing takes a parameter val. However, the function is called without it, with do_thing(). So, how is the parameter supposed to be used?

luiz00martins avatar Dec 19 '22 15:12 luiz00martins

Found a solution:

local async = function (fn)
	return function (...)
		local args = {...}

		return function (cb)
			assert(type(fn) == "function", "type error :: expected func")
			local thread = co.create(fn)
			local step = nil
			step = function (...)
				local stat, ret = co.resume(thread, ...)
				assert(stat, ret)
				if co.status(thread) == "dead" then
					return (cb or function (r) return r end)(ret) -- Added a return
				else
					assert(type(ret) == "function", "type error :: expected func")
					return ret(step) -- Added a return
				end
			end
			return step(table.unpack(args)) -- We pass the arguments here
		end
	end
end

The main disadvantage, is that now "bare" async functions have to be called two times to startup:

local async_fn = a.sync(function(a,b,c)
	print(a,b,c)
end)

local async_fn2 = a.sync(function()
	await(async_fn(1,2,3))
	await(async_fn(4,5,6))
	await(async_fn(7,8,9))
end)

async_fn2()() -- Note the double call

-- Prints:
-- "
-- 1 2 3
-- 4 5 6
-- 7 8 9
-- "
local do_thing = a.sync(function (val)
  local o = 10
  return o + val
end)

print(do_thing(5)()) -- Note the double call

-- Prints "15"

luiz00martins avatar Dec 26 '22 01:12 luiz00martins

I made another approach with little change to pong.

Usage:

local task = function(...) -- define task function body first
  print(...)
  --or some `a.wait` here
end

local a_task = a.sync(task, "params", 233) -- build the thunk (pong) with params
a_task(<callback func>) -- run
--- OUTPUT
--  params 233

Modifies:

local pong = function (func, ...)   -- `wrap` unpacked the params from sync(...), so we need pickout real `<callback func>` from them

  assert(type(func) == "function", "type error :: expected func")
  
  -- do the pick
  local params = {...}
  local callback = table.remove(params)

  local thread = co.create(func)
  local step = nil
  step = function (...)
    local stat, ret = co.resume(thread, ...)
    assert(stat, ret)
    if co.status(thread) == "dead" then
      (callback or function () end)(ret)
    else
      assert(type(ret) == "function", "type error :: expected func")
      ret(step)
    end
  end
  step(table.unpack(params))   -- params here for coroutine entrance
end

timrockefeller avatar Jan 31 '23 08:01 timrockefeller

@timrockefeller I get an error trying to call it:

local task = function(...) -- define task function body first
  print(...)
  --or some `a.wait` here
end

local a_task = a.sync(task, "params", 233) -- build the thunk (pong) with params
a_task()

This yields:

lua: test.lua:23: attempt to call a number value
stack traceback:
        test.lua:23: in local 'step'
        test.lua:29: in function <test.lua:9>
        (...tail calls...)
        test.lua:94: in main chunk
        [C]: in ?

However, you can fix that by adding a nil check to wrap:

local wrap = function (func)
  assert(type(func) == "function", "type error :: expected func")
  local factory = function (...)
    local params = {...}
    local thunk = function (step)
      if not step then -- check nil here
        step = function (...) end
      end
      table.insert(params, step)
      return func(table.unpack(params))
    end
    return thunk
  end
  return factory
end

Still, await doesn't seem to work as expected. Running:

local f = a.sync(function(arg1, arg2, arg3)
	print('arg1: ' .. arg1)
	print('arg2: ' .. arg2)
	print('arg3: ' .. arg3)
end)

local g = async(function()
	a.wait(f('arg1', 'arg2', 'arg3'))
	a.wait(f('arg1', 'arg2', 'arg3'))
	a.wait(f('arg1', 'arg2', 'arg3'))
end)

g()

Yields an error: lua: attempt to concatenate a nil value (local 'arg1')

luiz00martins avatar Feb 07 '23 00:02 luiz00martins

I fixed it by switching to luajls See #10.

MarcWeber avatar Apr 01 '23 18:04 MarcWeber