javascriptiosswiftpromisejavascriptcore

Get value from JS Promise/async function from within a JSContext


I'm executing a JavaScript SDK from within a JSContext, I can't get values out of any of the SDK's asynchronous functions however. I can get a JavaScript promise out of the JSContext, but I can't figure out how to resolve it. I have tried many ways of getting the value from the Promise, but every one has failed.

If I try something like the following I get [object Promise] back:

return self.jsContext.evaluateScript("new Promise(resolve => { setTimeout(300, () => resolve([1, 2, 3])) })")!

If I chain then directly onto the JS I get [object Promise] still:

return self.jsContext.evaluateScript("new Promise(resolve => { setTimeout(300, () => resolve([1, 2, 3])) }).then(val => val.json())")

If I try to invoke the method from Swift, I get still get [object Promise]:

let jsPromise = self.jsContext.evaluateScript("new Promise(resolve => { setTimeout(300, () => resolve([1, 2, 3])) })")
let promiseResult = jsPromise?.invokeMethod("then", withArguments: ["val => { return val.json() }"])
return promiseResult!

If I declare a JS variable outside of the Promise, then pass the value to it from a Swift-invoked then call, I get the original value set to it (as expected but worth a try):

self.jsContext.evaluateScript("let tempVar = 'Nothing has happened yet!'")
let jsPromise = self.jsContext.evaluateScript("new Promise(resolve => { setTimeout(300, () => resolve([1, 2, 3])) })")
let promiseResult = jsPromise?.invokeMethod("then", withArguments: ["val => { tempVar = val }"])
let tempVar = self.jsContext.evaluateScript("tempVar")
return tempVar!

If I try and use top-level await and resolve the Promise to a variable, then pull that variable out of the JSContext, IU get a EXC_BAD_INSTRUCTION error:

let jsPromise = self.jsContext.evaluateScript("let someVar = await new Promise(resolve => { setTimeout(300, () => resolve([1, 2, 3])) })")
return self.jsContext.evaluateScript("someVar")!

Thanks in advance, and sorry if I'm missing something, still very new to Swift.


Solution

  • There are issues while mocking the Promise work flow inside JSContext. The functions like setTimout,setInterval etc. are not available in JSContext.

    However, you can call Swift code from Javascript by passing block into the JSContext. Here's a code snippet, that shows how you can find out errors in JSContext.

    var logValue = "" {
        didSet {
            print(logValue)
        }
    }
    //block we can pass to JSContext as JS function
    let showLogScript: @convention(block) (String) -> Void = { value in
        logValue = value
    }
    let jsContext = JSContext()
    
    //set exceptionHandler block
    jsContext?.exceptionHandler = {
        (ctx: JSContext!, value: JSValue!) in
        print(value)
    }
    //make showLog function available to JSContext
    jsContext?.setObject(unsafeBitCast(showLogScript, to: AnyObject.self), forKeyedSubscript: "showLog" as (NSCopying & NSObjectProtocol))
    
    jsContext!.evaluateScript("showLog('this is my first name')") //this works
    jsContext!.evaluateScript("showLog(setTimeout.name)") //it has issue