如何在Swift中通过JavaScriptCore等待异步JS函数执行完成
问题描述
我想用JavaScriptCore在Swift中调用异步JS函数,但当前调用仅返回[object Promise],请问如何让Swift函数等待该异步JS函数执行完成?以下是我的尝试代码:
import SwiftUI import JavaScriptCore class Cube { func getNfts(pubKey: String) async -> String? { let jsCode = """ async function lookupNfts(pubKey, callback) { try { const config = { apiKey: "API KEY", network: Network.MATIC_MUMBAI, }; const alchemy = new alc.Alchemy(config); const { nfts } = await fetch(alchemy.nft.getNftsForOwner(pubKey)); callback(nfts.ownedNfts) return nfts.ownedNfts //swiftCallback(nfts); } catch (error) { console.log(error); } }; """ let context = JSContext() context!.evaluateScript(jsCode) guard let alc = Bundle.main.path(forResource: "alchemy", ofType: "js") else { print("Unable to read resource file") return nil } let functionInJS = context!.objectForKeyedSubscript("lookupNfts") let _ = print(functionInJS) let testBlock : @convention(block) (JSValue?) -> Void = { calledBackValue in let _ = print("calledBackValue:", calledBackValue) } let callback = JSValue.init(object: testBlock, in: context) let res = functionInJS!.call(withArguments: [pubKey, callback]).toString() let _ = print(res) return res } }
struct ContentView: View { var myvar = "hello"; @State private var sourceCode = "Loading…" var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) //Cube().getNfts(pubKey: "wallet address") Text(sourceCode ?? "No result") } .padding() .task{ let mycube = Cube() var res = ""; do { let data = await mycube.getNfts(pubKey: "mypubkey") ?? "nothinghere" sourceCode = String(data) } catch { sourceCode = "Failed to fetch site." } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
(我是Swift新手)
解决方案
JS的async函数本质会返回Promise对象,直接调用只会拿到Promise实例而非最终结果。我们可以通过**Swift的Continuation**将JS的异步回调逻辑转换为Swift的async/await风格,让Swift代码能等待JS异步任务完成。
1. 重构Cube类的getNfts方法
利用withCheckedThrowingContinuation挂起Swift任务,直到JS异步操作完成:
import SwiftUI import JavaScriptCore class Cube { func getNfts(pubKey: String) async throws -> String? { let jsCode = """ async function lookupNfts(pubKey) { try { const config = { apiKey: "API KEY", network: Network.MATIC_MUMBAI, }; const alchemy = new alc.Alchemy(config); // 注意:alchemy方法本身返回Promise,无需额外包裹fetch const nfts = await alchemy.nft.getNftsForOwner(pubKey); return nfts.ownedNfts; } catch (error) { throw error; } }; """ let context = JSContext() // 先加载Alchemy的JS资源文件 guard let alcPath = Bundle.main.path(forResource: "alchemy", ofType: "js"), let alcScript = try? String(contentsOfFile: alcPath) else { print("无法读取Alchemy资源文件") return nil } context?.evaluateScript(alcScript) // 再加载自定义JS函数 context?.evaluateScript(jsCode) guard let lookupNftsFunc = context?.objectForKeyedSubscript("lookupNfts") else { print("找不到JS函数lookupNfts") return nil } // 通过Continuation等待JS异步任务完成 return try await withCheckedThrowingContinuation { continuation in // 调用JS异步函数,获取Promise对象 let promise = lookupNftsFunc.call(withArguments: [pubKey]) // 绑定Promise的成功回调 promise?.invokeMethod("then", withArguments: [ JSValue(object: { (result: JSValue?) in let resultStr = result?.toString() ?? "无数据" continuation.resume(returning: resultStr) } as @convention(block) (JSValue?) -> Void, in: context) ]) // 绑定Promise的失败回调 promise?.invokeMethod("catch", withArguments: [ JSValue(object: { (error: JSValue?) in let errorMsg = error?.toString() ?? "JS调用失败" continuation.resume(throwing: NSError(domain: "JSCallError", code: -1, userInfo: [NSLocalizedDescriptionKey: errorMsg])) } as @convention(block) (JSValue?) -> Void, in: context) ]) } } }
2. 调整ContentView的调用逻辑
因为getNfts现在会抛出错误,需要完善do-catch处理逻辑:
struct ContentView: View { @State private var sourceCode = "Loading…" var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text(sourceCode) } .padding() .task{ let mycube = Cube() do { let data = try await mycube.getNfts(pubKey: "mypubkey") ?? "nothinghere" sourceCode = data } catch { sourceCode = "获取NFT失败:\(error.localizedDescription)" } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
关键说明
- 移除了JS函数中的自定义回调,直接利用Promise的
then/catch与Swift Continuation联动,符合异步逻辑的原生设计。 - 修正了JS代码错误:
alchemy.nft.getNftsForOwner(pubKey)本身返回Promise,不需要额外包裹fetch。 withCheckedThrowingContinuation让Swift的async函数挂起,直到JS异步操作完成,完美适配Swift的async/await语法。
内容的提问来源于stack exchange,提问作者Julia
相关产品推荐
相关产品推荐

