Yield across metamethod workaround?

Hello developers! I am working on a Object Oriented Trello API at the moment. However, I stumbled upon a huge obstacle, which might make it impossible, however, I am not sure.

Basically, I have a metatable with the .__index metamethod in it. It allows me to “read” properties from Trello objects. Such as Board.Name, etc. However, there is an error:

attempt to yield across metamethod/C-call boundary

It happens when I try to HTTP GET from the metamethod, is there a way to work around this limitation?

2 Likes

Why not just create a method yourself? Metamethods can’t and shouldn’t yield, so it’s probably better to write your own method

4 Likes

Do you mean something like Board:GetName() ?

1 Like

Yup

1 Like

Welp, guess I have to give up on this one, I was hoping to be able to create an API that used metamethods to read or edit properties on Trello. Thank you.

1 Like

I believe you could fetch the board while using a method like @Crazyman32 said?

Perhaps I’m wrong but couldn’t you just return a table from your method that includes the name, etc…

local Board = Trello:GetBoard()
Board.Name = "Hey!" 

I could be wrong but that seems like it’d work as long as you have a way in your index to actually update the boards name.

2 Likes

Only downside to this is that it won’t necessarily work both ways. For setting data, you’d still have to use the method approach.

In my opinion, using methods makes it more clear about what’s happening. If I access or write to something using dot-notation, I don’t expect it to yield. Getters and setters are good for these things. So in terms of OOP, I think it’s better to approach it with the Java-like coding practices (getters/setters), rather than C# (properties with implicit getters/setters).

6 Likes

Update:
After doing some research, I found out that this is a limitation in Lua 5.1.
In Lua 5.3 and LuaJIT, you can yield in metamethods.

I hope that some day ROBLOX updates to either. I understand it will be very difficult to do so, but I believe it would bring a lot of possibilities in regards of scripting. Stuff like bitwise operators, yielding in metamethods, etc.

1 Like

You can make it a callback function and put the yielding part in a switch function or a coroutine. For example:

function fn()

end
local trello = {}
setmetatable(trello, {__index = function(self, i)
if i == 'Name' then
 spawn(function()
  local res = http:PostAsync....
  fn(res)
 end)
end
end})

Something similar to that approach can work, but I really don’t see why you need to use the meta method for this.

1 Like

Another solution would be to immediately return a “promise” or another object which will later be updated when the response is ready. For example, you could write:

-- A method to wait for the value
local name = Board.Name.value()

or

-- A callback when the value is ready
Board.Name.ready(print)

or 

-- functions that use promises wait for their value
local function newPrint(a)
    if isPromise(a) then
        print(a.value())
    else
        print(a)
    end
end

local name = Board.Name
newPrint(name)

Promises are a common functional programming pattern (and therefore naturally better than OOP methods which I have a personal vendetta against).

2 Likes

Using PostAsync is not a problem here, I already took care of that part.
The problem is GetAsync, as it needs to wait for the value to be returned.

I am using metamethods to integrate Trello to ROBLOX in a way that would make it seem it is part of ROBLOX, in this case, manipulating the objects in the same manner you would manipulate any other object in the game.

I will look into this, thank you.

I have said PostAsync as an example. It’s will also work for GetAsync.

What does the 3rd argument “fn” in the __index function actually do?

I finally got a chance to test the code, fn is nil.
The 3rd argument is only used in __newindex.

fn is supposed to be an external callback function.
If you insist on using meta-methods for this project, I have created a small code for you. This is as close as you can go with meta-methods yielding.

local http = game:GetService'HttpService'
local mt = {}

setmetatable(mt, {
	__newindex = function(self, i, name)
		if i == 'Name' then
			spawn(function()
				-- HTTP Post code to change the name
			end)
		else
			self[i] = name
		end
	end,
	__index = function(self, i)
		if i == 'Name' then
			local t = {}
			function t:wait()
				return http:GetAsync'...' -- The http get request
			end
			return t
		end
	end,
})

mt.Name = 'something'
print(mt.Name:wait())

Ah, thank you. This will work wonderfully!
I might make the function :Get()

1 Like

You are welcome. You could also create a function that will regularly update the name and save yourself from yielding. But this is less efficient as it will use more requests(Cant compare really, that depends on the usage).

I did think about that, but then there’s the rate limits.

Yeah, alright