Categories
Modern JavaScript

Best of Modern JavaScript — Resolve and Reject Promises

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

Other Ways of Creating Promises

Other than using the Promise constructor, we can create promises in other ways.

One way is the Promise.resolve method.

It takes the resolved value of the promise as its argument.

And it returns a promise.

For instance, we can write:

Promise.resolve(1)  
  .then(x => console.log(x));

x is the resolved value from Promise.resolve .

If x is a promise whose construction is the receiver, then the promise is unchanged.

For instance, if we have:

const p = new Promise(() => null);  
console.log(Promise.resolve(p) === p);

then we get true from the console log.

If the argument is a thenable and then the then method in the argument is a function, then the resolved value with Promise.resolve is the argument that we call the then parameter with.

For example, if we have:

const thenableObj = {  
  then(reaction) {  
    reaction('foo');  
  }  
};  
const promise = Promise.resolve(thenableObj);  
console.log(promise instanceof Promise);   
promise.then(x => console.log(x));

We have a thenableObj , which has the then method.

It takes a reaction function and we called it with 'foo' .

Then we pass in an object to the Promise.resolve method, which returns a promise.

If we check if promise is an instance of a Promise , then that returns true .

We can also call then on it with a callback to get the 'foo' value, which is assigned to x .

Promise.reject()

We can call Promise.reject() to return a promise that’s rejected with a value.

For example, we can write:

const error = new Error('error');  
Promise.reject(error)  
  .catch(err => console.log(err === error));

We pass in an error object by passing that into the Promise.reject method.

Then we call catch by passing in a callback into it.

err has the error object that we called Promise.reject with, which should be the same as error .

Chaining Promises

We can chain promises with then as long as we return a promise.

Whatever we return in the then callback will be the resolved value of the promise.

For instance, if we have:

Promise.resolve('bar')  
  .then(function(value1) {  
    return 'foo';  
  })  
  .then(function(value2) {  
    console.log(value2);  
  });

Then value2 would be 'foo' .

This lets us flatten promises chains.

Instead of writing:

Promise.resolve('bar')  
  .then(function(value1) {  
    Promise.resolve('foo')  
      .then(function(value2) {  
        //...  
      });  
  })

We write:

Promise.resolve('bar')  
  .then(function(value1) {  
    return 'foo';  
  })  
  .then(function(value2) {  
    //...  
  });

Catching Errors

We can catch errors with the catch method.

For example, we can write:

Promise.reject(new Error('error'))  
  .catch(function() {  
    return 'error occurred';  
  })  
  .then(function(value) {  
    //...  
  });

We called catch to catch the error from the rejected promise and run our own code.

This lets us set some value for the next promise to use.

Throw an Exception

We can throw an exception in the then callback, then the promise returned will be rejected.

For instance, we can write:

Promise.resolve()  
  .then(function(value) {  
    throw new Error();  
  })  
  .catch(function(reason) {  
    // ...  
  });

We threw an error in the then callback, so the catch callback will run.

Conclusion

We can resolve a promise with a value so the next one can be invoked.

Also, we can reject promises and catch the error with catch .

Categories
Modern JavaScript

Best of Modern JavaScript — Proxy Handlers

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.

Function Proxy Handlers

We can also add handler methods to the handler we pass into the Proxy constructor or Proxy.revocable method.

apply(target, thisArgument, argumentsList) lets us change the behavior of the apply or call method.

It can also change the behavior of direct calls to a function.

target is the target object which we want to control.

thisArgument lets us pass in the value of this in the function.

argumentsList lets us pass in a list of arguments.

Invariants of Handler Methods

Handler methods have various invariants enforced on them.

They include the following.

apply(target, thisArgument, argumentsList) has no invariants enforced on them.

construct(target, argumentsList, newTarget) must return an object, and not null or a primitive value.

defineProperty(target, propKey, propDesc) must not let us add [properties to an object that isn’t extensible.

The target must have a property must have a non-configurable own property with propKey if configurable is false .

If writable and configurable are both false , then we can’t write a new property to the property.

deleteProperty(target, propKey) can’t delete non-configurable own properties of the target .

get(target, propKey, receiver) must return the property’s value if it has an own, non-writable, and non-configurable data property with name propKey .

Otherwise, it must return undefined .

getOwnPropertyDescriptor(target, propKey) must return either an object or undefined .

Non-configurable now properties of the target can’t be reported as non-existent.

If the target is non-extensile, then it must report as existing.

It can’t report a non-configurable property as configurable and can’t report a different value for a non-configurable and non-writable property.

getPrototypeOf(target) must return either an object or null .

If the target isn’t extensible, then the handler must return the prototype of the target object.

has(target, propKey) mustn’t hide a non-configurable own property of target .

If the target is on-extensible than no own property of target may be hidden.

isExtensible(target) must return a boolean.

The coerced boolean value must be the same as target.isExtensible .

ownKeys(target) must return an object which is treated as array-like and converted to an array.

Each element of the result must either be a string or a symbol.

The result must have the keys of all non-configurable non-inherited properties of target .

If target isn’t extensible then the result must only the keys of its own properties.

preventExtensions(target) returns a boolean.

target.isExtensible() must return false after this is called.

set(target, propKey, value, receiver) should set the property of a writable, configurable property of target .

If target already has an own non-configurable property with name propKey , then a TypeError should be thrown since the property can’t be set.

setPrototypeOf(target, proto) ‘s returned result is coerced into a boolean.

If the target isn’t extensible, then its prototype can’t be changed.

In this case, target ‘s prototype must be the same as what it is now, or a TypeError would be thrown.

Conclusion

The proxy handler methods must follow some conditions even though it lets us customize those operations.

This way, the object operations are still predictable.

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.