LinusU / swift-napi-bindings

MIT License
29 stars 0 forks source link

How to write async code in the native side? #1

Closed X140Yu closed 5 years ago

X140Yu commented 5 years ago

Hey @LinusU,

I want to use this library to expose Swift written code to electron, when it came with sync callback, it works perfectly, but when it came with async callback, there is one use case I haven’t figure out, that is how to write async code in Swift side and call the js code back.

Example code shows below(I know there is no Runloop in the native side, so I use NAPI.RunLoop.),

func callbackWithAsync(env: OpaquePointer, callback: Function) throws -> Void {
    try NAPI.RunLoop.ref(env)

    var context: napi_async_context!
    try napi_async_init(env, callback.napiValue(env), nil, &context)

    URLSession.shared.dataTask(with: URL(string: "https://jsonplaceholder.typicode.com/posts")!) { (data, response, error) in
        let res = String(data: data!, encoding: .utf8)!

        DispatchQueue.main.async {
            try! callback.call(env, res)
            NAPI.RunLoop.unref()
        }

    }.resume()
}

On the js side,

addon = require('addon.node')

describe("callback", () => {
  it("async", done => {
    mbox.callbackWithAsync(str => {
      console.log(str);
      done()
    });
  })
);

When I try to call the function above, it raised an error,

Fatal error: 'try!' expression unexpectedly raised an error: NAPI.Error.invalidArg: file file.swift, line xx
[1]    33377 illegal hardware instruction  mocha index.js

I guess there is something wrong with the env because the thread and the call stack changes. But I don't know how to handle it.

I know there are some methods like napi_create_async_work, napi_queue_async_work in N-API for the native addon to expose async code to node.js. But in this case, I'm a little bit confused with the relationship between node's eventloop and native addon's thread thing, and wonder if it is possible to make the example code above work?

LinusU commented 5 years ago

Hey!

First of all, really great to see someone using this module 😄

Unfortunately, I haven't quite figure out how to marry the two event loops in a great way. After spending some time on it, I opted for the current "hack" which isn't super great.

I would recommend looking at the source code for my largest project using these bindings, marionettjs. It's a small wrapper over another library, and basically every function is asynchronous.

There I've used PromiseKit on the Swift side to represent a Promise on the JS side. I actually think that I want to add that code directly to this project, but I'm also thinking that I should wait and see if there will be a native Swift type for a Promise/Future. You can see how I've done this here: https://github.com/LinusU/marionettejs/blob/master/Sources/MarionetteJS/Promise%2BNAPI.swift

Now, to the specific problem at hand, would you mind telling me which try! is raising the NAPI.Error.invalidArg? Basically, where you wrote "line XX", which line does that correspond to?

I won't have too much more time today, but I'll try to copy your code and see if I can make it run on my computer when I get the time 👌

X140Yu commented 5 years ago

I've read the code you written in marionettejs, used it in my own project and it works like a charm!

I'll use this solution to solve my problem and close this issue. Really appreciate your great work, I learned a lot from it.

Thanks! 😃