Skip to content

Instantly share code, notes, and snippets.

@yycking
Last active January 20, 2024 08:54
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save yycking/e01cf2d73533c5fb752ffdfa511723fa to your computer and use it in GitHub Desktop.
Save yycking/e01cf2d73533c5fb752ffdfa511723fa to your computer and use it in GitHub Desktop.
add fetch, console.log and Promise.then/catch to JavaScriptCore on iOS/Mac
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))
""")
@jigneshD11
Copy link

There is some error

JS Error: Optional(TypeError: fetch("https://github.com")
.then is not a function. (In 'fetch("https://github.com")
.then(data=>{
    console.log(data)
})', 'fetch("https://github.com")
.then' is undefined))

@yycking
Copy link
Author

yycking commented Jun 17, 2022

@jigneshD11 These is not work in playground.

@jedt
Copy link

jedt commented Jan 20, 2024

guard let context = JSContext.plus else {
        fatalError("Could not create JSContext")
    }

context.evaluateScript("""
(function () {
    fetch("https://jsonplaceholder.typicode.com/todos/1")
        .then((data) => {
            console.log(data);
        })
        .catch((e) => console.log(e));
})();
""")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment