【问题标题】:How would one do async JavaScript getters and setters?如何做异步 JavaScript getter 和 setter?
【发布时间】:2015-05-01 16:20:02
【问题描述】:

想想 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(....);

或者我们会做更多的角度风格,我们会自动将其解析为一个值?

总而言之,我们如何实现异步 getter?

【问题讨论】:

  • 我想不出除了返回承诺之外的任何其他方式。
  • 可能是yield 的生成器?
  • @elclanrs: AFAIK getter 不能成为生成器。
  • 这是我的观点,也许我对 JS 很老套,但如果里面有动词,我想在调用签名中看到动词(如 get)或括号。我认为通过隐藏括号,您可以使它看起来像 RAM 数据对象的同步读取,但事实并非如此。 customer.orders.then(....); 会更直观(对我来说)customer.getOrders().then(....);customer.orders.get(fnThen_or_callBack);customer.orders(fnThen_or_callBack);,或者只是customer.orders().then(....);。在所有这些情况下,我一眼就能看出正在发生的事情超出了简单的阅读范围。
  • @trusktr 令人印象深刻。 JS 的一大优势一直是它的异步性。它最大的弱点之一一直是它无法在需要时真正进行同步。这肯定会有所帮助。

标签: javascript promise


【解决方案1】:

以下是实现获取订单功能的方法

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...
});

【讨论】:

  • 不利用属性描述符 get/set - 这只是将您自己的方法添加到在某些方面相似但不同的对象上。
【解决方案2】:

getset 函数关键字似乎与 async 关键字不兼容。然而,由于async/await 只是Promises 的包装,您可以只使用Promise 使您的函数“await-able”。

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


吸气剂

Promise 与 getter 配合得很好。

在这里,我使用了 Node.js 8 内置的 util.promisify() 函数,它在一行中将节点样式回调(“nodeback”)转换为 Promise。这使得编写await-able 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);
})();

二传手

对于二传手来说,这有点奇怪。

您当然可以将 Promise 作为参数传递给 setter,然后在其中执行任何操作,无论您是否等待 Promise 实现。

但是,我想一个更有用的用例(把我带到这里的那个!)将用于设置器,然后 awaiting 该操作将在使用设置器的任何上下文中完成。不幸的是,这是不可能的,因为 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

【讨论】:

  • TMHO,getter 确实被正确地承诺了,但我认为 setter 的例子并不现实:如果提供的值是一个承诺 - 在你调用 set 之前先取消它... 很简单;但是,如果我们谈论的过程中值设置 inside set 方法包括一些异步工作,那就是另一回事了。然后,我们最好用异步嵌套函数包装逻辑并返回它的返回值(这是一个承诺)。
  • @AviTshuva 在传给二传手之前没有理由放弃承诺。这工作得很好。是的,在做一些异步工作时,使用函数可能比使用 setter 更好,但使用 setter 在技术上仍然可行。
  • 但这就是我所争论的:不是可能性,而是缺乏意义。
  • 您是否应该在代码中实际使用setters 和getters 超出了这个问题的范围
  • 我同意,因此我说的是评论;它超出了范围,但在实践中,它是一个重要且相关的 tmho。
【解决方案3】:

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

const object = {};

Object.defineProperty(object, 'myProperty', {

    async get() {

        // Your awaited calls

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

相反,当涉及到异步设置器时,就会出现问题。 由于表达式a = b 总是产生b,所以没有什么可以避免这种情况,即持有属性a 的对象中没有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;
}

它返回一个异步属性的描述符,给定另一个描述符,允许定义一些看起来像异步设置器的东西。

你可以使用上面的代码如下:

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');
})();

【讨论】:

    【解决方案4】:

    以下内容允许代理处理程序中的异步设置器遵循 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
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-29
      • 1970-01-01
      • 2011-09-29
      • 2017-03-31
      相关资源
      最近更新 更多