import JavaScriptCore extension JSContext { subscript(key: String) -> Any { get { return self.objectForKeyedSubscript(key) } set{ self.setObject(newValue, forKeyedSubscript: key as NSCopying & NSObjectProtocol) } } } @objc protocol JSConsoleExports: JSExport { static func log(_ msg: String) } class JSConsole: NSObject, JSConsoleExports { class func log(_ msg: String) { print(msg) } } @objc protocol JSPromiseExports: JSExport { func then(_ resolve: JSValue) -> JSPromise? func `catch`(_ reject: JSValue) -> JSPromise? } class JSPromise: NSObject, JSPromiseExports { var resolve: JSValue? var reject: JSValue? var next: JSPromise? var timer: Timer? func then(_ resolve: JSValue) -> JSPromise? { self.resolve = resolve self.next = JSPromise() self.timer?.fireDate = Date(timeInterval: 1, since: Date()) self.next?.timer = self.timer self.timer = nil return self.next } func `catch`(_ reject: JSValue) -> JSPromise? { self.reject = reject self.next = JSPromise() self.timer?.fireDate = Date(timeInterval: 1, since: Date()) self.next?.timer = self.timer self.timer = nil return self.next } func fail(error: String) { if let reject = reject { reject.call(withArguments: [error]) } else if let next = next { next.fail(error: error) } } func success(value: Any?) { guard let resolve = resolve else { return } var result:JSValue? if let value = value { result = resolve.call(withArguments: [value]) } else { result = resolve.call(withArguments: []) } guard let next = next else { return } if let result = result { if result.isUndefined { next.success(value: nil) return } else if (result.hasProperty("isError")) { next.fail(error: result.toString()) return } } next.success(value: result) } } extension JSContext { static var plus:JSContext? { let jsMachine = JSVirtualMachine() guard let jsContext = JSContext(virtualMachine: jsMachine) else { return nil } jsContext.evaluateScript(""" Error.prototype.isError = () => {return true} """) jsContext["console"] = JSConsole.self jsContext["Promise"] = JSPromise.self let fetch:@convention(block) (String)->JSPromise? = { link in let promise = JSPromise() promise.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) {timer in timer.invalidate() if let url = URL(string: link) { URLSession.shared.dataTask(with: url){ (data, response, error) in if let error = error { promise.fail(error: error.localizedDescription) } else if let data = data, let string = String(data: data, encoding: String.Encoding.utf8) { promise.success(value: string) } else { promise.fail(error: "\(url) is empty") } }.resume() } else { promise.fail(error: "\(link) is not url") } } return promise } jsContext["fetch"] = unsafeBitCast(fetch, to: JSValue.self) return jsContext } } guard let jsContext = JSContext.plus else {exit(-1)} jsContext.exceptionHandler = { context, exception in print("JS Error: \(String(describing: exception))") } jsContext.evaluateScript(""" fetch("https://github.com") .then(data=>{ console.log(data) }) .catch(e=>console.log(e)) """)