You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 07:05:33