import JavaScriptCore extension JSContext { subscript(key: String) -> Any { get { return self.objectForKeyedSubscript(key) as Any } 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) } } class JSPromise: NSObject { enum JSPromiseResult { case success(Any) case failure(Any) } private var result: JSPromiseResult? { didSet {result.map(report)} } private var callbacks: [(JSPromiseResult) -> Void] = [] func observe(using callback: @escaping (JSPromiseResult) -> Void) { if let result = result { return callback(result) } callbacks.append(callback) } private func report(result: JSPromiseResult) { callbacks.forEach { $0(result) } callbacks = [] } convenience init(executor: @escaping (@escaping(Any)->Void, @escaping(Any)->Void) -> Void) { self.init() executor {[weak self] resolve in self?.result = .success(resolve) } _: {[weak self] reject in self?.result = .failure(reject) } } override init() { } } @objc protocol JSPromiseExports: JSExport { func then(_ resolve: JSValue) -> JSPromise func `catch`(_ reject: JSValue) -> JSPromise } extension JSPromise: JSPromiseExports { func then(_ block: JSValue) -> JSPromise { let weakBlock = JSManagedValue(value: block, andOwner: self) let promise = JSPromise() observe { result in switch result { case .success(let value): let next = weakBlock?.value.call(withArguments: [value]) as Any promise.result = .success(next) case .failure(let error): promise.result = .failure(error) } } return promise } func `catch`(_ block: JSValue) -> JSPromise { let weakBlock = JSManagedValue(value: block, andOwner: self) let promise = JSPromise() observe { result in switch result { case .success(let value): promise.result = .success(value) case .failure(let error): let next = weakBlock?.value.call(withArguments: [error]) as Any promise.result = .failure(next) } } return promise } } 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 return JSPromise{ resolve, reject in if let url = URL(string: link) { URLSession.shared.dataTask(with: url){ (data, response, error) in if let error = error { reject(error.localizedDescription) } else if let data = data, let string = String(data: data, encoding: String.Encoding.utf8) { reject(string) } else { reject("\(url) is empty") } }.resume() } else { reject("\(link) is not url") } } } jsContext["fetch"] = unsafeBitCast(fetch, to: JSValue.self) return jsContext } } jsContext.evaluateScript(""" fetch("https://github.com") .then(data=>{ console.log(data) }) .catch(e=>console.log(e)) """)