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.

Categories
Modern JavaScript

Best of Modern JavaScript — Handler Methods

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at how to control JavaScript object operations.

Make Objects Non-Configurable

We can make objects non-configurable.

This means that we can’t change the property descriptor of an object.

The property descriptors include the property value, whether the property is writable, or whether it’s configurable.

Configurable means the ability to change the property descriptions of an object.

To disable configurability, we can write:

const obj = {};  
Object.defineProperty(obj, 'foo', {  
  value: 'bar',  
  writable: false,  
  configurable: false  
});

We defined the obj.foo property with the Object.defineProperty method.

The 3rd argument has the property descriptors.

configurable set to false lets us disable changing property descriptors.

We also set writable to false so that we can’t change the value of the foo property’s value.

The enumerate Trap

ES6 proxies was originally going to include the enumerate trap to lets us change the behavior of the for-in loop.

But it’s removed to simplified proxies.

The Proxy API

There’re 2 ways to create proxies.

One way is to use the Proxy constructor.

We can write:

const proxy = new Proxy(target, handler)

The Proxy constructor takes a target object and the handler object to let trap the object operations.

Another way is to use the Proxy.revocable factory function.

We can write:

const {  
  proxy,  
  revoke  
} = Proxy.revocable(target, handler)

to use it.

The difference is that there’s the revoke function to let us revoke access to the proxy.

Handler Methods

The handler object can have various methods to let us control various object operations.

defineProperty(target, propKey, propDesc) lets us control how properties are defined with Object.defineProperty .

target is the target object that the proxy controls.

propKey has the property key.

propDesc is an object with property descriptors.

It returns a boolean.

deleteProperty(target, propKey) lets us control how properties are defined.

They have the same first 2 parameters as defineProperty .

It lets us control how the delete operator works.

get(target, propKey, receiver) lets us control how properties are retrieved.

receiver is the proxy object.

It returns the property value that we want to return.

getOwnPropertyDescriptor(target, propKey) lets us control how property descriptors are returned.

We can use it to return it our way.

It controls how Object.getOwnPropertyDescriptor(proxy, propKey) acts.

getPrototypeOf(target) returns the prototype of the target object.

It controls how Object.getPrototypeOf(proxy) acts.

has(target, propKey) lets us control how the in operator acts.

It should return a boolean.

isExtensible(target) lets us modify the behavior of the Object.isExtensible(proxy) method.

It should return a boolean.

ownKeys(target) can change the behavior of several methods.

It returns an array of property keys.

It controls the behavior of Object.getOwnPropertyPropertyNames(proxy) to return string keys.

Also, it can control the behavior of Object.getOwnPropertyPropertySymbols(proxy) to return symbol keys.

And, it can control how the Object.keys(proxy) method behaves.

preventExtensions(target) lets us change the behavior of Object.preventExtensions(proxy) .

It returns boolean.

set(target, propKey, value, receiver) controls how object properties are set.

It also returns a boolean.

setPrototypeOf(target, proto) lets us change the behavior of the Object.setPrototypeOf(proxy, proto) method.

Conclusion

We can control many object operations with proxy handler methods.

Object static methods can also be controlled with proxy handler methods.

Categories
Modern JavaScript

Best of Modern JavaScript — Control 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.

Handle Negative Array Indices

With proxies, we can enhance arrays by letting us access arrays with negative indexes.

For instance, we can write:

const handler = {
  get(target, propKey, receiver) {
    const index = Number(propKey);
    if (index < 0) {
      propKey = String(target.length + index);
    }
    return target[propKey];
  }
};

const target = [];
target.push(1, 2, 3);
const arr = new Proxy(target, handler);

console.log(arr[-1])

We create the handler object with the get method.

If we have an index that’s less than 0, then we make it positive by adding the target.length to it.

Then we return the property of array with the index of the array.

Next, we create the target array with the arguments.

Then we return a proxy which is an array-like object that we can use negative indexes with.

Setting Properties

The set method in the handler object lets us control how we set properties in an object.

For example, we can write:

function createArray(callback) {
  const array = [];
  return new Proxy(array, {
    set(target, propertyKey, value, receiver) {
      callback(propertyKey, value);
      return Reflect.set(target, propertyKey, value, receiver);
    }
  });
}
const arr = createArray(
  (key, value) => console.log(key, value));
arr.push('a');

to create an array where we can watch the operations of it.

We have a createArray function that takes a callback that’s called whenever we set a value on the array.

The callback is called with the propertyKey and value .

Then we see the array operations logged as they’re being done.

Revocable References

We can create revocable references so that we can access objects only when we’re allowed.

For example, we can write:

const target = {};
const handler = {};
const {
  proxy,
  revoke
} = Proxy.revocable(target, handler);

target is an object that we can create a proxy with.

handler is an object to let us intercept and change the object operations.

proxy is the proxy returned.

And revoke is a function that lets us revoke access to the proxy.

Meta Object

JavaScript objects have hidden properties that let us get objects.

There’s a method to let us get the object’s properties and return it.

They can trap get and call operations.

Enforcing Invariants for Proxies

We can enforce various kinds of invariants with proxies.

One thing we can do is to disable the extensibility of objects.

We can use the Object.preventExtensions method to disable adding properties to objects.

For instance, we can write:

'use strict'
const obj = Object.preventExtensions({});
obj.bar = 'foo';

to stop us from adding properties to an object.

If strict mode is on, then we get the error ‘Uncaught TypeError: Cannot add property bar, object is not extensible’ if we try to add a property to a non-extensible object.

We can check if an object is extensible by writing:

console.log(Object.isExtensible(obj));

If it returns true , then we can add properties to it.

Conclusion

We can do various things with proxies, like controlling access to an object and more.

For other operations, we don’t need proxies. The Object constructor has static methods to let us control object operations.