Can't read from a file using JSPromise
revolter opened this issue · 4 comments
I know that this is more of a support issue than a bug report, but I really don't know what else to try, and didn't find another help channel.
Here is the entire script using Tokamak:
import JavaScriptKit
import TokamakDOM
struct TokamakApp: App {
var body: some Scene {
WindowGroup("Tokamak App") {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
VStack {
HTML("input", [
"id": "file",
"type": "file"
])
Button("Convert") {
let document = JSObject.global.document
let input = document.getElementById("file")
let file = input.files.item(0).object!
let promise = file.text.function!.callAsFunction().object!
JSPromise<JSValue, Error>(promise)!.then { value in
let console = JSObject.global.console.object!
let log = console.log.function!
log(value)
}
}
}
}
}
// @main attribute is not supported in SwiftPM apps.
// See https://bugs.swift.org/browse/SR-12683 for more details.
TokamakApp.main()
which is throwing this error:
Unhandled Promise Rejection: TypeError: Can only call Blob.text on instances of Blob
Running
document.getElementById("file").files.item(0).text().then(text => console.log(text))
works though.
The problem is here:
let promise = file.text.function!.callAsFunction().object!
The use of callAsFunction
is unnecessary and causes the text
method to not get the proper this
value internally. Here’s the standard way to do this in JSKit:
let promise = file.text().object!
Indeed, though I actually had to change it to:
let promise = file.text!().object!
But now I'm getting:
[Error] Fatal error: The function was already released: file JavaScriptKit/JSFunction.swift, line 292
[Error] Unhandled Promise Rejection: RuntimeError: Unreachable code should not be executed (evaluating 'exports.swjs_call_host_function(host_func_id, argv, argc, callback_func_ref)')
I fixed it by changing it to:
let jsPromise = JSPromise<JSValue, Error>(promise)!
jsPromise.then { value in
let console = JSObject.global.console.object!
let log = console.log.function!
log(value)
// Without this, `jsPromise` gets released before this
// closure gets called.
print(jsPromise)
}
but I feel like it's not the best solution.
I think that’s the best we can do for now. There isn’t currently a cross-browser supported way for us to keep an object alive on the Swift side as long as its corresponding JS object remains alive. So you have to keep a strong reference to the JSObjectRef
inside Swift if you want JS to be able to call into it. Once Safari gains support for FinalizationRegistry
, it should be possible for us to automatically hold onto Swift objects until they’re no longer reachable from either Swift or JS.