如何做异步JavaScript getter和setter?

时间:2015-03-01 06:16:51

标签: javascript promise

想想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是同步,而在JS中,如果在我们的例子中,合理的话,我们将进入数据库,我们将这样做 async 。 / p>

我们如何创建异步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?

4 个答案:

答案 0 :(得分:7)

getset功能关键字似乎与async关键字不兼容。但是,由于async / await只是Promise的包装,因此您可以使用Promise使您的函数“await - 能够”。

注意:应该可以使用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);
})();

设定器

对于制定者来说,它有点奇怪。

你当然可以将一个Promise传递给一个setter作为一个参数并在里面做任何事情,无论你是否等待Promise得到满足。

但是,我想一个更有用的用例(将我带到这里的那个!)将用于setter,然后await该操作将在任何上下文中使用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秒后打印到控制台,Foo在此之后打印2秒。这是因为await正在等待promisedValue完成,并且它永远不会在设置器中看到Promise使用/生成。

答案 1 :(得分:0)

对于异步获取器,您可以执行以下操作:

const object = {};

Object.defineProperty(object, 'myProperty', {

    async get() {

        // Your awaited calls

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

当涉及到异步设置器时,就会出现问题。 由于表达式a = b总是产生b,因此无法避免这种情况,即,持有属性a的对象中没有设置器可以覆盖此行为。
由于我也偶然发现了这个问题,因此我发现异步设置器实际上是不可能的。因此,我意识到我必须选择一种替代设计来代替异步设置器。然后我想到了以下替代语法:

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

答案 2 :(得分:0)

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

答案 3 :(得分:-2)

以下是如何实施获取订单功能

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