I have an class that contains db methods and it's wrapped in a proxy which handles access to properties. Since the issue is related to promises here is an simplified example code that is reproducing the same issue:
const handler = {
ownKeys(target) {
return Object.keys(target._attributes)
},
get(target, property) {
console.log(`<-${property}`) // <-- this logs what properties are being accessed
if (typeof target[property] !== 'undefined') {
return Reflect.get(target, property)
}
return Reflect.get(target._attributes, property)
},
set(target, property, value) {
target._attributes[property] = value
return true
}
}
class User {
static table = 'users'
static fetch(query = {}, opts = {}) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(new this(query))
}, 500)
})
}
constructor(attributes = {}) {
this._attributes = attributes
return new Proxy(this, handler)
}
}
async function trigger() {
const user = await User.fetch({test:'test'})
console.log(JSON.stringify(user._attributes))
}
trigger()
Everything works well, during the testing I've added a printout to the proxy to determine performance hit of using such model design, and I noticed that my model get's called from within promise chain.
Example output follows:
<-then
<-_attributes
{"test":"test"}
I guess that returning new this(query)
causes the promises to think that maybe it's a promise returned and consequently .then()
is executed.
Only workaround that I've found is to wrap resolve response inside new array or another object like this:
static fetch(query = {}, opts = {}) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([new this(query)])
}, 500)
})
}
// Output
// 2
// <-_attributes
// {"test":"test"}
What I'm wondering is, is this correct way and are there other solutions to this side effect?
All objects passed to as a result of a promise is always checked to see if it has a
then
property. If it does, that function is used to queue up entries to get a final value. This is why logic likelogs
45
instead of a promise object. Since the promise objects has a.then
property, it is used to unwrap the promises value. The same behavior happens in yourresolve(new this(query))
case, because it needs to know if there is a value to unwrap.As you've said, commented in your post, you could certainly wrap the instance with a non-proxy, e.g.
which would check for
.then
on that object instead of on your proxy, but then you have to do.value
to actually get the proxy, which can be a pain.At the end of the day, that is a choice you'll have to make.