如何做异步 JavaScript 的 getter 和 setter?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28790744/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How would one do async JavaScript getters and setters?
提问by deitch
Think of how Rails, e.g. allows you to define a property as associated with another:
想一想 Rails 如何允许你定义一个与另一个相关联的属性:
class Customer < ActiveRecord::Base
has_many :orders
end
This does not set up a database column for orders
. Instead, it creates a getter for orders
, which allows us to do
这不会为 设置数据库列orders
。相反,它创建了一个 getter for orders
,它允许我们做
@orders = @customer.orders
Which goes and gets the related orders
objects.
哪个去获取相关的orders
对象。
In JS, we can easily do that with getters:
在 JS 中,我们可以使用 getter 轻松做到这一点:
{
name: "John",
get orders() {
// get the order stuff here
}
}
But Rails is sync, and in JS, if in our example, as is reasonable, we are going to the database, we would be doing it async.
但是 Rails 是同步的,而在 JS 中,如果在我们的示例中,按照合理的方式,我们要访问数据库,我们将执行异步操作。
How would we create async getters (and setters, for that matter)?
我们将如何创建异步 getter(和 setter,就此而言)?
Would we return a promise that eventually gets resolved?
我们会返回一个最终得到解决的承诺吗?
{
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;
}
}
which would allow us to do
这将使我们能够做
customer.orders.then(....);
Or would we do it more angular-style, where we would automatically resolve it into a value?
或者我们会做更多的角度风格,我们会自动将它解析为一个值?
To sum, how do we implement async getters?
总而言之,我们如何实现异步 getter?
回答by Cameron Tacklind
The get
and set
function keywords seem to be incompatible with the async
keyword. However, since async
/await
is just a wrapper around Promise
s, you can just use a Promise
to make your functions "await
-able".
该get
和set
功能的关键字似乎有不兼容的async
关键字。但是,由于async
/await
只是Promise
s的包装器,您可以使用 aPromise
使您的函数“ await
-able”。
Note: It should be possible to use the Object.defineProperty
method to assign an async
function to a setter or getter.
注意:应该可以使用该Object.defineProperty
方法将async
函数分配给setter 或 getter。
getter
吸气剂
Promises work well with getters.
Promise 与 getter 配合得很好。
Here, I'm using the Node.js 8 builtin util.promisify()
function that converts a node style callback ("nodeback") to a Promise
in a single line. This makes it very easy to write an await
-able 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);
})();
setter
二传手
For setters, it gets a little weird.
对于二传手来说,这有点奇怪。
You can of course pass a Promise to a setter as an argument and do whatever inside, whether you wait for the Promise to be fulfilled or not.
您当然可以将 Promise 作为参数传递给 setter 并在内部执行任何操作,无论您是否等待 Promise 完成。
However, I imagine a more useful use-case (the one that brought me here!) would be to use to the setter and then await
ing that operation to be completed in whatever context the setter was used from. This unfortunately is not possible as the return value from the setter function is discarded.
但是,我想一个更有用的用例(把我带到这里的那个!)是使用 setter,然后在使用 setter 的await
任何上下文中完成该操作。不幸的是,这是不可能的,因为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!');
})();
In this example, the Done!
is printed to the console after 1
second and the Foo
is printed 2
seconds after that. This is because the await
is waiting for promisedValue
to be fulfilled and it never sees the Promise
used/generated inside the setter.
在这个例子中,秒后Done!
打印到控制台,1
然后Foo
打印2
秒。这是因为await
正在等待promisedValue
完成并且它永远不会Promise
在 setter 中看到used/generated 。
回答by Davide Cannizzo
As for asynchronous getters, you may just do something like this:
至于异步 getter,你可以这样做:
const object = {};
Object.defineProperty(object, 'myProperty', {
async get() {
// Your awaited calls
return /* Your value */;
}
});
Rather, the problem arises when it comes to asynchronous setters.
Since the expression a = b
always produce b
, there is nothing one can do to avoid this, i.e. no setter in the object holding the property a
can override this behavior.
Since I stumbled upon this problem as well, I could figure out asynchronous setters were literally impossible. So, I realized I had to choose an alternative design for use in place of async setters. And then I came up with the following alternative syntax:
相反,当涉及到异步 setter 时,问题就出现了。由于表达式a = b
总是产生b
,没有什么可以做来避免这种情况,即持有该属性的对象中的任何 settera
都不能覆盖这一行为。
因为我也偶然发现了这个问题,所以我可以弄清楚异步 setter 几乎是不可能的。所以,我意识到我必须选择一种替代设计来代替异步设置器。然后我想出了以下替代语法:
console.log(await myObject.myProperty); // Get the value of the property asynchronously
await myObject.myProperty(newValue); // Set the value of the property asynchronously
I got it working with the following code,
我用下面的代码让它工作,
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;
}
which returns a descriptor for an asynchronous property, given another descriptor that is allowed to define something that looks like an asynchronous setter.
它返回一个异步属性的描述符,给定另一个描述符,允许定义看起来像异步设置器的东西。
You can use the above code as follows:
您可以使用上面的代码如下:
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);
}
})
});
Once you've set up with an asynchronous property like the above, you can set it as already illustrated:
一旦你设置了一个像上面那样的异步属性,你就可以像已经说明的那样设置它:
(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');
})();
回答by Davide Cannizzo
Here's how you could implement your get orders function
以下是您如何实现获取订单功能
function get(name) {
return new Promise(function(resolve, reject) {
db.find("orders", {customer: name}, function(err, data) {
if (err) reject(err);
else resolve(data);
});
});
}
You could call this function like
你可以像这样调用这个函数
customer.get("John").then(data => {
// Process data here...
}).catch(err => {
// Process error here...
});