I want to use Vapor(a Web framework made with Swift) and JavaScriptCore(Apple's JS engine) to process some requests. Vapor supports async/await, which makes handling HTTP requests very easy, like this:
func routes(_ app: Application) throws {
app.get("myrequestpath") { req async throws -> String in
let result = await myAsyncFunction()
return result
}
}
myAsyncFunction() handles the processing to return the desired result and some of the processing I do is in JavaScript, executed in JavaScriptCore. I have no problem with interacting with JavaScriptCore, I can call the JS functions and JS can call the Swift functions I give access to.
I want to be able to initiate a JS function from Swift, wait for the async code in JS to finish its job and then pass the result to Swift async code for further processing.
The problem is, JavaScript itself has async/await that works slightly differently than the one in Swift.
In JavaScript, async functions return a Promise object that later resolves once the code block inside it calls the resolve or reject functions.
As a result, I can't simply await a JS function because it will return a Promise object right away and Swift will continue execution:
func myAsyncFunction() async -> Bool {
let jsContext = JSEngine().evaluateScript(myJSCode)
let result = await jsContext.objectForKeyedSubscript("myAsyncJSFunction").call(withArguments: [data])
return result
}
I would love to do that but I can't, the result will be a Promise object that doesn't mean anything in Swift(right).
As a result, what I need is, to write an async Swift function that pauses until the JS function calls back the resolve or reject function.
Something like this maybe?:
func myAsyncFunction() async -> Bool {
let jsContext = JSEngine().evaluateScript(myJSCode)
let result = await {
/*
A function that I can pass to JS and the JS can call it when it's ready, return the result to Swift and continue execution
*/
}
return result
}
Any ideas how to achieve that?
Okay, as it turns out, Swift async/await concurrency does have a support for continuation. It's not mentioned on the main article on Swift documentation and most 3rd party articles bill this feature as a way to integrate the old callback centric concurrency with the new async/await API, however this is much more useful than simply integrating the old code with the new one.
Continuation provides you with an asynchronous function that can be called from within your async function to pause execution with await until you explicitly resume it. That means, you can await input from the user or another library that uses callback to deliver the results. That also means, you are entirely responsible to correctly resume the function.
In my case, I'm providing the JavaScriptCore library with a callback that resumes execution from Swift, which is called by an async JavaScript code upon completion.
Let me show you. This is a JS code that asynchronously processes a request:
To be able to pause the Swift execution until the JS code finishes processing and continue once the JS calls the callback with the data, I can use continuation like this: