fengari-lua / fengari

🌙 φεγγάρι - The Lua VM written in JS ES6 for Node and the browser

Home Page:https://fengari.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to await function in js side

frank-zsy opened this issue · comments

Here is the question, I set a global function to Lua use lua_setglobal which read args from stack and then call js function and return the result. But if the js function returns a Promise, I can not resolve it in Lua side, I really want to auto resolve js function call so Lua side won't need to concern such thing.

The code is

set = (funcName, func) => {
  wrapped = () => {
    args = [...]; // read args from stack
    res = func.call(null, ...args);  // res can be a promise, support await here?
    pushStack(L, res);
    return resNumber;  // push the res to stack and return res number
  };
  lua.lua_pushjsfunction(L, wrapped);
  lua.lua_setglobal(L, funcName);
};

set('add', (a, b) => a + b);

If the return value is not a Promise, then everything works fine here, but if the return value is a Promise, I can't resolve on Lua side and if I add await before func.call and make wrapped an async function, then I receive error message even the injected function returns a number value.

Any thought on this?

I think the only way is to run Lua code in a Lua coroutine, and then use lua_yield and lua_resume in your Javascript implementation.

It's the only way I found to make asynchronous call from Lua (callback or promises, it doesn't change much, it's the same thing in the end).

Now I don't have PC at hand to show you some working code, but maybe you just need a point in the right direction 😄

@lorenzos thank you very much, I will look into Lua coroutine and lua_yield, lua_resume, it looks good!

@lorenzos Thank you, I just figured out and I will record it here in case anyone else has the same question.

If you want to make async calls in js to be a sync function from Lua, you need to use coroutine and yield from outside the Lua and resume until the Promise return, here is some code that would solve the problem.

set = (funcName, func) => {
  wrapped = (L) => {
    args = [...]; // read args from stack
    res = func.call(null, ...args);
    if (res instance of Promise && lua.lua_isyieldable(L)) { // if return a Promise and currently in a coroutine
      // if not in a coroutine, yield will throw an error about 'yield outside coroutine'
      Promise.resolve(res).then(r => {
        if (r === undefined) lua.lua_resume(L, from, 0);  // no return value
        else {
          pushStack(L, r)
          lua.lua_resume(L, from, 1);  // only one return value
        }
      });
      return lua.lua_yield(L, 0);  // yield from outside to pause Lua code
    } else {
      pushStack(L, res);
      return resNumber;  // push the res to stack and return res number
     }
  };
  lua.lua_pushjsfunction(L, wrapped);
  lua.lua_setglobal(L, funcName);
};

set('add', async (a, b) => {
  await waitFor(10);  // wait for 10ms, which makes this function async and return a promise, Lua call will pause here to wait for return
  return a + b;
});

Then you can use add in Lua code like this

local co = coroutine.create(function() -- must wrapped in a coroutine or you need to start Lua script as coroutine from js
  local r = add(1, 2)  -- add will return after 10ms pause, and you can still get the result to r
  print(r)
end)
coroutine.resume(co)

@daurnimator a higher level JS library wrapper to support this idiom could be nice...

@daurnimator a higher level JS library wrapper to support this idiom could be nice...

sounds like fengari-lua/fengari-interop#2

@daurnimator a higher level JS library wrapper to support this idiom could be nice...

sounds like fengari-lua/fengari-interop#2

Why I didn't find this repo sooner, I almost write the interop part from scratch myself 😓