Promise 作为 class 方法调用在解析时触发 object.then

Promise as a class method call triggers object.then upon resolving

我有一个包含数据库方法的 class,它被包装在处理属性访问的代理中。由于这个问题与承诺有关,这里是一个简化的示例代码,它重现了同样的问题:

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()

一切正常,在测试期间,我向代理添加了打印输出以确定使用此类模型设计的性能影响,并且我注意到我的模型是从承诺链中调用的。

示例输出如下:

<-then
<-_attributes
{"test":"test"}

我想返回 new this(query) 会导致 promise 认为它可能是返回的 promise,因此 .then() 被执行。 我发现的唯一解决方法是将解析响应包装在新数组或另一个对象中,如下所示:

static fetch(query = {}, opts = {}) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([new this(query)])
    }, 500)
  })
}
// Output
// 2
// <-_attributes
// {"test":"test"}

我想知道的是,这是正确的方法吗?对于这种副作用还有其他解决方案吗?

始终检查作为承诺结果传递给的所有对象,以查看它是否具有 then 属性。如果是,则该函数用于对条目进行排队以获得最终值。这就是为什么逻辑像

Promise.resolve()
   .then(() => {
       return Promise.resolve(45);
   })
   .then(result => {
       console.log(result);
   });

logs 45 而不是 promise 对象。由于 promise 对象有一个 .then 属性,它用于解包 promises 值。同样的行为发生在你的 resolve(new this(query)) 案例中,因为它需要知道是否有要解包的值。

正如您所说,在您的 post 中评论,您当然可以使用非代理包装实例,例如

resolve({ value: new this(query) })

这将在 that 对象上检查 .then 而不是在你的代理上,但是你必须做 .value 才能真正获得代理,这可能会很痛苦。

归根结底,这是您必须做出的选择。