Categories
Modern JavaScript

Best of Modern JavaScript — Proxy and Object

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at metaprogramming with JavaScript proxies.

Revocable Proxies

We can create revocable proxies with ES6.

To do that, we call the Proxy.revocable method with the target and handler arguments.

For example, we can write:

const target = {};
const handler = {
  get(target, propKey, receiver) {
    return 'foo';
  },

  has(target, propKey) {
    return true;
  }
};
const {
  proxy,
  revoke
} = Proxy.revocable(target, handler);

The Proxy.revocable function takes the target object which we want to change the behavior of.

handler is an object which has various methods to let us control the behavior of target .

It returns an object with the proxy object and the revoke function to revoke the proxy.

We can call revoke to stop using the proxy.

For example, if we have:

const target = {};
const handler = {
  get(target, propKey, receiver) {
    return target[propKey];
  },

has(target, propKey) {
    return true;
  }
};
const {
  proxy,
  revoke
} = Proxy.revocable(target, handler);

proxy.foo = 'bar';
console.log(proxy.foo);

revoke();

console.log(proxy.foo);

Then the 2nd console log will give us an error since the proxy has been revoked with the revoke function.

Therefore, we get ‘Uncaught TypeError: Cannot perform ‘get’ on a proxy that has been revoked’.

Proxies as Prototypes

We can use proxies as a prototype of an object.

For example, we can write:

const target = {};
const handler = {
  get(target, propKey, receiver) {
    return target[propKey];
  },

  has(target, propKey) {
    return true;
  }
};
const proto = new Proxy(target, handler);
const obj = Object.create(proto);

to use the Object.create method to create a proxy object and use that as the prototype.

Forwarding Intercepted Operations

We can forward intercepted operations with proxies.

For example, we can write:

const target = {};
const handler = {
  deleteProperty(target, propKey) {
    return delete target[propKey];
  },
  has(target, propKey) {
    return propKey in target;
  },
}

const proto = new Proxy(target, handler);

to add the forward the delete operation and the in operation.

We just do the same thing that we expect without the proxy within the handler methods.

Wrapping an Object Affects this

Since the handler is an object, it has its own value of this .

Therefore, if we have:

const target = {
  foo() {
    console.log(this === target)
    console.log(this === proxy)
  }
};
const handler = {};
const proxy = new Proxy(target, handler);

proxy.foo()

Then the first console log is false and the 2nd is true since we called foo on proxy .

On the other hand, if we have:

const target = {
  foo() {
    console.log(this === target)
    console.log(this === proxy)
  }
};
const handler = {};
const proxy = new Proxy(target, handler);

target.foo()

Then the first console log is true and the 2nd is false .

Objects that can’t be Wrapped Transparently

If an object has some private data, then a proxy can’t wrap the object transparently.

For example, if we have:

const _name = new WeakMap();
class Animal {
  constructor(name) {
    _name.set(this, name);
  }
  get name() {
    return _name.get(this);
  }
}

Then if we have:

const mary = new Animal('mary');
const proxy = new Proxy(mary, {});
console.log(proxy.name);

We get that the name property in undefined .

This is because the name is stored in the WeakMap instead of as a direct property of this itself.

this is the proxy so it doesn’t have the name property.

Conclusion

Proxies can be revocable, and the value of this are different between the proxy and the original object.

Categories
Modern JavaScript

Best of Modern JavaScript — Proxies and Object Operations

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at metaprogramming with JavaScript proxies.

Wrapping Instances of Built-in Constructors

We can use proxies to wrap instances of built-in constructors.

For instance, we can write:

const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

But if we write:

console.log(proxy.getDate());

We get ‘Uncaught TypeError: this is not a Date object.’ because we called getDate on the proxy instead of the original Date instance.

However, proxies can be wrapped transparently with arrays.

For instance, we can write:

const p = new Proxy(new Array(), {});
p.push('foo');
console.log(p.length);

We created a proxy with the Array constructor.

The handler object is an empty object.

We call push with to insert an entry into the array.

It works with the proxy.

And we get the length with as we expected.

For objects that can’t be transparently wrapped with proxies, we can modify the handler so that it does what we expect.

For instance, we can write:

const handler = {
  get(target, propKey, receiver) {
    if (typeof target[propKey] === 'function') {
      return target[propKey].bind(target);
    }
    return target[propKey]
  },
};

const target = new Date();
const proxy = new Proxy(target, handler);

Then we call the target ‘s propKey method.

In the get method, we check if target[propKey] ‘s type is a 'function' .

If it is, then we change the value of this with bind so that the function will have the correct value of this .

Otherwise, we just return the value as-is.

Now we can call the getDate method on the object without issue:

const handler = {
  get(target, propKey, receiver) {
    if (typeof target[propKey] === 'function') {
      return target[propKey].bind(target);
    }
    return target[propKey]
  },
};

const target = new Date();
const proxy = new Proxy(target, handler);

console.log(proxy.getDate());

Use Cases for Proxies

We can use proxies to log the operations of the object.

For example, we can write:

const handler = {
  get(target, propKey, receiver) {
    console.log(target[propKey])
    if (typeof target[propKey] === 'function') {
      return target[propKey].bind(target);
    }
    return target[propKey]
  },
};

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  toString() {
    return `Person(${this.firstName}, ${this.lastName})`;
  }
}

const target = new Person('jane', 'smith');
const proxy = new Proxy(target, handler);

console.log(proxy.toString());

to trace the object operation.

We get the structure of the toString method.

And then we get the returned result with it.

We can also use proxies to warn about unknown properties.

For instance, we can write:

const target = {};
const handler = {
  get(target, propKey, receiver) {
    if (!(propKey in target)) {
      throw new Error('property does not exist');
    }
    return target[propKey];
  }
};

const proxy = new Proxy(target, handler);
console.log(proxy.foo);

We have a handler object that has a get method.

Inside it, we use the in operator to check if there’s a property with the name propKey .

Since there’s no foo property in target , we get ‘Uncaught Error: property does not exist’.

This is a handy feature that doesn’t in JavaScript objects natively.

Conclusion

We can use proxies to log object operations and intercept object operations in various ways.

Categories
Modern JavaScript

Best of Modern JavaScript — Proxies

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at metaprogramming with JavaScript proxies.

Metaprogramming

Metaprogramming is writing code that processes our app-level code.

App-level code is code that takes user input and returns output for the user.

There’re various kinds of metaprogramming.

One kind is introspection. This is where we have read-only access to the structure of a program.

Self-modification is where we change the program’s structure.

Intercession is where we redefine the semantics of some language operation.

An example of introspection in Javascript is using Object.keys() to get the keys of the object.

An example of self-modification is to move a property from one place to another.

For instance, we can write:

function moveProperty(source, prop, target) {
  target[prop] = source[prop];
  delete source[prop];
}

to move the prop property from the source object to the target object.

Then we delete the source object’s prop property.

Proxies let us do intercession with our JavaScript app.

We can change how object operations are done with it.

Proxies

Proxies bring intercession to JavaScript.

We can do many operations with an object.

We can get the property value, and we can set it.

Also, we can check if property is in an object.

We can customize these operations with proxies.

For example, we can write:

const target = {};
const handler = {
  get(target, propKey, receiver) {
    console.log(target, propKey, receiver);
    return 'foo';
  }
};
const proxy = new Proxy(target, handler);

console.log(proxy.bar)

We create an empty target object that we use with the proxy.

And the handler object has a get method that lets us return a value for the properties given.

target is the object we passed into the first argument of the Proxy constructor.

propKey is the property name.

So if we get the value of proxy.bar or any other property of proxy , we get 'foo' returned.

We can also change the way that we check if a property exists in an object.

For example, we can write:

const target = {};
const handler = {
  has(target, propKey) {
    console.log(propKey);
    return true;
  }
};
const proxy = new Proxy(target, handler);

console.log('bar' in proxy);
console.log('foo' in proxy);

Then we get the target and propKey parameters, which hold the same values as the ones in get .

We returned true so whenever we use the in operator with it, it returns true .

The set method can let us change how objects are set.

Intercepting Method Calls

We can intercept method calls by calling the methods in object properties our way.

For instance, we can write:

const target = {
  foo(text) {
    return `text ${text}`;
  }
};

const handler = {
  get(target, propKey, receiver) {
    const origMethod = target[propKey];
    return function(...args) {
      const result = origMethod.apply(this, args);
      console.log(JSON.stringify(args), JSON.stringify(result));
      return result;
    };
  }
};
const proxy = new Proxy(target, handler);

console.log(proxy.foo('baz'));

We call the target.foo method within th get method of the handler .

Inside the get method, we call the method with the arguments but with this being the proxy instead of the target object.

This way, we can change the behavior of the method call.

We logged the arguments and the results of it.

It’s handy for logging method calls.

Conclusion

Proxies let us run control how object properties are get and set.

We can also change the way that properties are check.

Categories
Modern JavaScript

Best of Modern JavaScript — Promise Mistakes to Avoid

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at JavaScript promise mistakes we should avoid.

Chaining and Errors

We can have one or more then calls in a chain.

If we have multiple then calls that don’t have error handlers, then the error is passed on until there’s an error handler.

So if we have:

asyncFunc1()
  .then(asyncFunc2)
  .then(asyncFunc3)
  .catch(function(reason) {
    // ...
  });

Then catch will be called if any of the promises in the chain has an error.

Common Mistakes

A promise chain can be built with a chain of then calls.

One way we can make a mistake is that we return the original promise instead of the promise that’s returned from then .

For example, we shouldn’t write:

function foo() {
  const promise = promiseFunc();
  promise.then(result => {
    //....
  });

  return promise;
}

Instead, we should write:

function foo() {
  const promise = promiseFunc();
  return promise.then(result => {
    //...
  });
}

We got to return the promise chain instead of the original promise.

Nesting Promises

We should never nest promises. The whole point of using promises is to avoid nesting.

For example, if we have something like:

promiseFunc1()
  .then(result1 => {
    promiseFunc2()
      .then(result2 => {
        //...
      });
  });

We should rewrite it to:

promiseFunc1()
  .then(result1 => {
    return promiseFunc2();
  })
  .then(result2 => {
    //...
  });

Creating Promises Instead of Chaining

We shouldn’t create promises instead of chaining.

So instead of creating promises the Promise constructor:

function insert(entry) {
  return new Promise((resolve, reject) => {
    db.insert(entry)
      .then(resultCode => {
        //...
        resolve(resultCode);
      }).catch(err => {
        reject(err);
      })
  });
}

We just return the promise chain:

function insert(entry) {
  return db.insert(entry)
    .then(resultCode => {
      //...
    }).catch(err => {
      handleError(err);
    })
}

What we shouldn’t do is create a new promise by using the Promise constructor.

then already returns a promise, so we don’t need the Promise constructor.

Using then() for Error Handling

then shouldn’t be used for handing.

catch(callback) is short for then(null, callback) .

However, using in the same line is problematic.

The error callback in the then method doesn’t catch errors that occur in the rejection by the fulfillment callback.

For instance, if we have:

promiseFunc1()
  .then(
    value => {
      fooo();
      return promiseFunc2();
    },
    error => {
      //...
    });

If an error occurs in the first then callback, the 2nd one won’t catch it.

Error Handling

There’re 2 kinds of errors that can occur.

Operational errors happen when a correct program encounters an exceptional situation.

For example, if someone enters something in an invalid format, then that’s an operational error.

A programmer error is an incorrect behavior in our program.

For instance, we may be passing in an array to a function when it actually expects a string, then that’s a programmer error.

To handle operational errors, we should either throw exceptions or invoke rejections.

If it’s a programmer error, then the program should fail quickly.

We can throw an exception to make it clear that the program failed.

Conclusion

There’re many common mistakes that we should avoid with promises.

Categories
Modern JavaScript

Best of Modern JavaScript — Promise API

Since 2015, JavaScript has improved immensely.

It’s much more pleasant to use it now than ever.

In this article, we’ll look at some JavaScript promise methods.

finally

The finally method lets us run code in a promise chain regardless of the outcome of the promise chain.

For instance, we can write:

asyncFn()  
  .then(function(value1) {  
    //...  
  })  
  .then(function(value2) {  
    //...  
  })  
  .finally(function() {  
    // ...  
  });

We call finally so that we can run a callback that runs regardless of the outcome.

This is useful for running clean up code and something like that.

ES6 Promise Libraries

There’re many promise libraries we can use.

Many of them conform to the ES6 API.

They include:

ES6 Promise API

The ES6 promise API has various parts.

The most basic part is the Promise constructor.

It takes a callback that has the resolve and reject functions as parameters.

resolve resolves the promise with the passed in value.

It’ll be forwarded to the then callback if then is called.

reject rejects the promise with the given value.

The argument of reject is usually an Error instance.

We can use it by writing:

const p = new Promise(function(resolve, reject) {  
  //...  
});

Static Promise Methods

There’re several static promise methods in the API.

They include the Promise.resolve and Promise.reject methods.

Promise.resolve converts an arbitrary value to a promise.

We pass in an argument to it to return a value with the resolved value.

We can call Promise.resolve with:

Promise.resolve(1)

Promise.reject lets us create a new promise that’s rejected with a reason.

We can call Promise.reject with:

Promise.reject(1)

There’re several methods to compose promises.

Promise.all and Promise.race lets us run multiple promises in different ways.

Promise.all takes an iterable and is fulfilled if all elements in the array of promises are fulfilled.

If any of the promises in the iterable are rejected, then the rejection value is the first rejection value from the promises.

Promise.race also takes an iterable which settles the returned promise.

It’ll return a promise that resolves to the value that’s first resolved.

Promise Instance Methods

Promise instance has various methods.

The Prototype.prototype.then(onFulfilled, onRejected) method takes a onFulfilled and onRejected callbacks.

onFulfilled is called immediately when the promise is resolved.

onRejected is called when the promise is rejected.

then returns a new promise that’s resolved to the value returned in the promise.

If the then callback throws an exception, then the promise returned is rejected.

If onFulfilled is omitted, then the fulfilled result is forwarded to the next then .

If onRejected is omitted, the rejection is forwarded to the next then .

Promise.prototype.catch(onRejected) lets us catch the error of the first rejected promise.

It’s the same as p.then(null, onRejected).

Conclusion

The Promise API is a standard API that lets us write async code easily.

They can be chained and we can catch errors with them.