Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How would one do async JavaScript getters and setters?

Think of how Rails, e.g. allows you to define a property as associated with another:

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 = @customer.orders 

Which goes and gets the related orders objects.

In JS, we can easily do that with getters:

{    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.

How would we create async getters (and setters, for that matter)?

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?

like image 314
deitch Avatar asked Mar 01 '15 06:03

deitch


People also ask

Can getters be async?

It can't. You cannot return synchronously from an asynchronous function. async/await is just syntactic sugar around promises + generators.

How do I use async in JavaScript?

Inside an async function, you can use the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, at which point the fulfilled value of the promise is treated as a return value, or the rejected value is thrown.

Are there getters and setters in JavaScript?

In JavaScript, accessor properties are methods that get or set the value of an object. For that, we use these two keywords: get - to define a getter method to get the property value. set - to define a setter method to set the property value.

What are asynchronous methods in JavaScript?

JavaScript provides three methods of handling asynchronous code: callbacks, which allow you to provide functions to call once the asynchronous method has finished running; promises, which allow you to chain methods together; and async/await keywords, which are just some syntactic sugar over promises.


2 Answers

The get and set function keywords seem to be incompatible with the async keyword. However, since async/await is just a wrapper around Promises, you can just use a Promise to make your functions "await-able".

Note: It should be possible to use the Object.defineProperty method to assign an async function to a setter or getter.


getter

Promises work well with getters.

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.

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.

However, I imagine a more useful use-case (the one that brought me here!) would be to use to the setter and then awaiting 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.

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.

like image 70
Cameron Tacklind Avatar answered Sep 23 '22 14:09

Cameron Tacklind


As for asynchronous getters, you may just do something like this:

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:

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'); })(); 
like image 22
Davide Cannizzo Avatar answered Sep 19 '22 14:09

Davide Cannizzo