想想 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?
get
和set
函数关键字似乎与async
关键字不兼容。但是,由于 async
/await
只是 Promise
的包装,因此您可以仅使用 Promise
来使您的函数“能够”。注意:应该可以使用
await
方法将
Object.defineProperty
函数分配给 setter 或 getter。
吸气剂在这里,我使用 Node.js 8 内置
async
函数,在一行中将节点样式回调(“nodeback”)转换为
util.promisify()
。这使得编写一个 Promise
可用的 getter 变得非常容易。await
二传手
你当然可以将 Promise 作为参数传递给 setter,并在里面做任何事情,无论你是否等待 Promise 被履行。
但是,我想象一个更有用的用例(将我带到这里的用例!)是使用 setter,然后
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 的任何上下文中完成该操作。不幸的是,这是不可能的,因为
setter 函数的返回值被丢弃。
await
在此示例中,
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
被满足,并且它永远不会看到在 setter 中使用/生成的 promisedValue
。Promise
但是,表达式
const object = {};
Object.defineProperty(object, 'myProperty', {
async get() {
// Your awaited calls
return /* Your value */;
}
});
始终会生成
a = b
,无法使用熟悉的语法创建异步 setter。您将不得不接受替代设计,例如:
b
可以按如下方式实现,
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);
let promise;
delete newDescriptor.set;
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) {
return key === 'then' || key === 'catch'
? addListener(key)
: 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 () => {
console.log('getting...');
console.log('value from getter is', await object.myProperty);
console.log('setting...');
await object.myProperty('My new value');
console.log('done');
})();
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
}
的使用(这是 TypeScript,只需删除设置返回值类型以获取 JS 的
await
位即可):: Promise<..>
// 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
})()
}
你可以这样调用这个函数
function get(name) {
return new Promise(function(resolve, reject) {
db.find("orders", {customer: name}, function(err, data) {
if (err) reject(err);
else resolve(data);
});
});
}