Closed i404788 closed 2 years ago
Promise is currently not supported; Promsie is a special object and needs to be verified to work well, including how asynchronous processing between different runtimes should be implemented.
I did some more experiments trying to use it as an Opaque object, and it does seem to work in some cases.
Doing await evalCode('v = foreignAsyncFunc(); return v')
works with no problem.
However doing the same with a callback doesn't work:
const depromisify = (x: Promise<T>, cb: (T) => any) => {x.then(cb)}
arena.expose({depromisify, foreignAsyncFunc})
evalCode('v = foreignAsyncFunc(); depromisify(v, console.log)')`
Is the marshalling different between evalCode and FFIs?
It took a bit of effort but I found a way to 'depromisify' classes and functions:
export function depromisifyFn<T>(fn: (...args: any[]) => Promise<T>, cls: any):
(...args: any[]) => (void|any) {
return (...args: any[]) => {
const cb: (v: T) => void = args[args.length - 1]
//console.log("Depromisifying call", fn, cls, cb)
const promise = fn.apply(cls, args.slice(0, args.length - 1))
//console.log(fn, promise)
if (!cb) {
return promise
}
if (promise && typeof promise.then === 'function' && promise[Symbol.toStringTag] === 'Promise') {
// is compliant native promise implementation
promise.then(cb).catch(cb)
}
else {
return promise
}
}
}
export function depromisifyClass(cls: any): any {
const excluded = ["constructor"]
let proxy: any = null
const wrapValue = (target: any, key: any, tag: string) => {
//console.log(tag, cls, target, key)
if (!excluded.includes(key)) {
if (typeof target[key] == "function") {
return depromisifyFn(target[key].bind(cls), cls)
}
}
return target[key]
}
const prototype_handler = {
getOwnPropertyDescriptor(target: any, prop: string) {
let desc: any = Object.getOwnPropertyDescriptor(target, prop)
desc.value = wrapValue(desc, 'value', 'proto_descriptor')
//console.log(desc)
return desc
},
get(target: any, key: string) {
return wrapValue(target, key, 'proto')
}
}
const prototype_proxy = new Proxy(Object.getPrototypeOf(cls), prototype_handler as any)
const handler = {
getPrototypeOf(target: any){
return prototype_proxy
},
get(target: any, key: string) {
if (key == '__proto__') return prototype_proxy
return wrapValue(target, key, 'self')
}
}
proxy = new Proxy(cls, handler)
return proxy
}
I can’t follow what “depromisify” means, but maybe this can help: In the base library, I recently added QuickJSVm.resolvePromise which turns a VM promise into a host promise. This was the missing dual to QuickJSVm.newPromise, which is how the host can construct a promise inside the guest.
The error path makes promise handling tricky - if a VM promise rejection is unmarshalled to a host promise rejection, the unmarshalling layer must be sure to free the rejection reason handle to avoid a leak. For this reason QuickJSVm.resolvePromise always resolves with a result object (and never rejects) to ensure the developer must consider the error handle.
I think building a bit of custom handling of values that are instanceof Promise into quickjs-emscripten-sync would be a good idea for that reason.
The depromisify function is specifically for the sync library, it will warp/proxy classes/functions (and classes' prototypes) such that the last argument will become a callback, and no Promise is returned unless no callback is added. This is the inverse functionality of the shim function promisify
.
I think it would be a lot better to use proper quickjs Promise handling though, since depromisify does make callback hell very common and it has some edge-cases due to the way it detects arguments.
Thank you guys. It's good news that QuickJSVM.resolvePromise
and QuickJSVM.newPromise
has been added to quickjs-emscripten. I'll try to implement it soon.
Any updates?
Would be nice to have this feature! 👍
v1.4 has been released. quickjs-emscripten v0.20~ and promises are supported. Check it!
Hey there,
I've had a blast using your library but noticed promises don't really work (resolving them inside QuickJS). Is there a limitation on this, or is it possible to use them?
Thanks
Error for reference:
TypeError: Promise.prototype.then called on incompatible Proxy