如何执行异步JavaScript getter和setter?

wztqucjr  于 2023-02-02  发布在  Java
关注(0)|答案(5)|浏览(140)

想想Rails是如何让你定义一个属性与另一个属性相关联的:

class Customer < ActiveRecord::Base
  has_many :orders
end

这不会为orders设置数据库列,而是为orders创建一个getter,这允许我们执行以下操作

@orders = @customer.orders

它将获取相关的orders对象。
在JS中,我们可以很容易地用getter来实现这一点:

{
   name: "John",
   get orders() {
     // get the order stuff here
   }
}

但是Rails是 sync,而在JS中,如果在我们的例子中,我们要访问数据库,我们会做 async
我们如何创建异步getter(和setter)?
我们会回报一个最终得到解决的承诺吗?

{
   name: "John",
   get orders() {
     // create a promise
     // pseudo-code for db and promise...
     db.find("orders",{customer:"John"},function(err,data) {
        promise.resolve(data);
     });
     return promise;
   }
}

这样我们就可以

customer.orders.then(....);

或者我们可以更有Angular 地处理它,自动地将它解析为一个值?
总而言之,我们如何实现异步getter?

vqlkdk9b

vqlkdk9b1#

getset函数关键字似乎与async关键字不兼容,但是,由于async/await只是Promise s的 Package 器,因此您可以只使用一个Promise来使您的函数“await-able”。

  • 注意:应该可以使用Object.defineProperty方法将async函数赋给setter或getter。*

吸气剂

承诺对吸气剂很有效。
这里,我使用Node.js 8内置的util.promisify()函数,它在一行中将节点样式的回调(“nodeback”)转换为Promise,这使得编写支持await的getter变得非常容易。

var util = require('util');
class Foo {
  get orders() {
    return util.promisify(db.find)("orders", {customer: this.name});
  }
};

// We can't use await outside of an async function
(async function() {
  var bar = new Foo();
  bar.name = 'John'; // Since getters cannot take arguments
  console.log(await bar.orders);
})();

设定器

对于二传手来说,这变得有点奇怪。
你当然可以把一个承诺作为一个参数传递给一个设定者,然后在里面做任何事情,不管你是否等待承诺的实现。
然而,我想象一个更有用的用例(就是我来到这里的那个用例!)是使用setter,然后await ing该操作在使用setter的任何上下文中完成。不幸的是,这是不可能的,因为setter函数的返回值被丢弃了

function makePromise(delay, val) {
  return new Promise(resolve => {
    setTimeout(() => resolve(val), delay);
  });
}

class SetTest {
  set foo(p) {
    return p.then(function(val) {
      // Do something with val that takes time
      return makePromise(2000, val);
    }).then(console.log);
  }
};

var bar = new SetTest();

var promisedValue = makePromise(1000, 'Foo');

(async function() {
  await (bar.foo = promisedValue);
  console.log('Done!');
})();

在本例中,Done!1秒后打印到控制台,Foo2秒后打印。这是因为await正在等待promisedValue完成,它从未看到在setter内部使用/生成的Promise

vbkedwbf

vbkedwbf2#

对于异步getter,您可以这样做:

const object = {};

Object.defineProperty(object, 'myProperty', {

    async get() {

        // Your awaited calls

        return /* Your value */;
    }
});

相反,当遇到异步setter时,问题就出现了,因为表达式a = b总是产生b,所以没有什么可以避免的,也就是说,对象中持有属性a的setter不能覆盖这个行为。
因为我也偶然发现了这个问题,所以我知道异步setter是不可能的,所以我意识到我必须选择一种替代设计来代替异步setter,然后我想出了下面的替代语法:

console.log(await myObject.myProperty); // Get the value of the property asynchronously
await myObject.myProperty(newValue); // Set the value of the property asynchronously

我用了下面的代码,

function asyncProperty(descriptor) {

    const newDescriptor = Object.assign({}, descriptor);

    delete newDescriptor.set;

    let promise;

    function addListener(key) {
        return callback => (promise || (promise = descriptor.get()))[key](callback);
    }

    newDescriptor.get = () => new Proxy(descriptor.set, {

        has(target, key) {
            return Reflect.has(target, key) || key === 'then' || key === 'catch';
        },

        get(target, key) {

            if (key === 'then' || key === 'catch')
                return addListener(key);

            return Reflect.get(target, key);
        }
    });

    return newDescriptor;
}

它返回异步属性的描述符,并给出另一个允许定义类似异步setter的描述符。
可以按如下方式使用上面的代码:

function time(millis) {
    return new Promise(resolve => setTimeout(resolve, millis));
}

const object = Object.create({}, {

    myProperty: asyncProperty({

        async get() {

            await time(1000);

            return 'My value';
        },

        async set(value) {

            await time(5000);

            console.log('new value is', value);
        }
    })
});

一旦你像上面那样设置了一个异步属性,你就可以像已经演示的那样设置它:

(async function() {

    console.log('getting...');
    console.log('value from getter is', await object.myProperty);
    console.log('setting...');
    await object.myProperty('My new value');
    console.log('done');
})();
siotufzp

siotufzp3#

下面的代码允许代理处理程序中的异步设置器遵循Davide Cannizzo答案中的约定。

var obj = new Proxy({}, asyncHandler({
  async get (target, key, receiver) {
    await new Promise(a => setTimeout(a, 1000))
    return target[key]
  },
  async set (target, key, val, receiver) {
    await new Promise(a => setTimeout(a, 1000))
    return target[key] = val
  }
}))

await obj.foo('bar') // set obj.foo = 'bar' asynchronously
console.log(await obj.foo) // 'bar'

function asyncHandler (h={}) {
  const getter = h.get
  const setter = h.set
  let handler = Object.assign({}, h)
  handler.set = () => false
  handler.get = (...args) => {
    let promise
    return new Proxy(()=>{}, {
      apply: (target, self, argv) => {
        return setter(args[0], args[1], argv[0], args[2])
      },
      get: (target, key, receiver) => {
        if (key == 'then' || key == 'catch') {
          return callback => {
            if (!promise) promise = getter(...args)
            return promise[key](callback)
          }
        }
      }
    })
  }
  return handler
}
zzlelutf

zzlelutf4#

下面是另一种方法,它创建了一个额外的 Package 器,但在其他方面它涵盖了我们所期望的内容,包括await的使用(这是TypeScript,只需去掉设置返回值类型的: Promise<..>位以获得JS):

// this doesn't work
private get async authedClientPromise(): Promise<nanoClient.ServerScope> {
    await this.makeSureAuthorized()
    return this.client
}

// but this does
private get authedClientPromise(): Promise<nanoClient.ServerScope> {
    return (async () => {
        await this.makeSureAuthorized()
        return this.client
    })()
}
wj8zmpe1

wj8zmpe15#

下面是实现get orders函数的方法

function get(name) {
    return new Promise(function(resolve, reject) {
        db.find("orders", {customer: name}, function(err, data) {
             if (err) reject(err);
             else resolve(data);
        });
    });
}

您可以像这样调用此函数

customer.get("John").then(data => {
    // Process data here...
}).catch(err => {
    // Process error here...
});

相关问题