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.

Categories
Modern JavaScript

Best of Modern JavaScript — Composing 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 handling JavaScript promise exceptions.

Handling Exceptions in Promise-Based Functions

We can handle exceptions in promise-based functions.

If exceptions are thrown in then and catch callbacks, then the methods convert them into rejections.

However, if we run the synchronous code before the promise code, then the whole function throws an exception.

This may be a problem that we want to solve.

One way to solve this is to surround the synchronous and promise code with try-catch.

For example, we can write:

function foo() {
  try {
    syncFn();
    return asyncFn()
      .then(result => {
        //...
      });
  } catch (err) {
    return Promise.reject(err);
  }
}

We have the foo function with the try-catch block which runs syncFn first and then asyncFn which returns a promise.

We return the promise chain in the try block.

And if the catch block is run, then we return a rejected promise.

This lets us keep the function async all the way through.

Run the Synchronous Code in the Callback

We can also run the synchronous code in the callback.

For instance, we can write:

function foo() {
  return asyncFn()
    .then(result => {
      syncFn();
      return asyncFn2();
    })
    .then(result => {
      //...
    });
}

If the synchronous syncFn functions run in the then callback and it throws an exception, then the returned promise will be rejected.

The start of the promise chain can also be in the Promise constructor.

For example, we can write:

function foo() {
  return new Promise((resolve, reject) => {
      syncFn();
      resolve(asyncFn());
    })
    .then(result => {
      //...
    });
}

We run syncFn in the Promise constructor callback so that we run the callback so that exceptions will be propagated if an error is thrown by syncFn .

Composing Promises

We can run multiple promises in various ways.

If we want to run multiple unrelated promises in parallel, we can use the Promise.all method.

For example, we can write:

Promise.all([
    asyncFn1(),
    asyncFn2(),
  ])
  .then(([result1, result2]) => {
    //...
  })
  .catch(err => {
    //...
  });

We pass in an array of promise-returning functions.

Then we pass that in Promise.all .

And then we call then with a callback that has an array of the resolved values of both values.

Then we call catch to catch the errors occurring with them.

We can loop through the resolved items with them.

For example, we can write:

Promise.all([
    asyncFn1(),
    asyncFn2(),
  ])
  .then((results) => {
    for (const result of results) {
      console.log(result);
    }
  })
  .catch(err => {
    //...
  });

We get the results array and looped through it with the for-of loop.

Promise.race()

Promise.race is another method to chain promises.

It takes an array of promises and returns a promise that’s resolved to the value of the promise that’s resolved first.

For example, we can write:

Promise.race([
    asyncFn(),
    delay(3000).then(function() {
      throw new Error('timed out')
    })
  ])
  .then(function(text) {
    //...
  })
  .catch(function(reason) {
    //...
  });

We have an array of promises with the asyncFn and delay which both return promises.

Then we call then with a callback to get the resolved value from the promise that’s resolved the earliest.

Conclusion

We can handle errors with promise-based functions in a few ways.

Also, we can run multiple promises in many different ways.

Categories
Modern JavaScript

Best of Modern JavaScript — Yield

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

The yield Keyword

The yield keyword can only be used in generator functions.

For example, we can write:

function* genFn() {
  yield 'foo';
  yield 'bar';
  return 'baz';
}

But we can’t write:

function* genFn() {
  ['foo', 'bar'].forEach(x => yield x);
}

We’ll get a syntax error.

Recursion with yield*

The yield* keyword lets us call another generator function within a generator function.

For instance, we can write:

function* genFn() {
  yield 'foo';
  yield 'bar';
  yield 'baz';
}

function* bar() {
  yield 'x';
  yield* genFn();
  yield 'y';
}

const gen = bar()

to call the genFn generator function within the bar generator function.

So if we use it by writing:

const arr = [...gen];

then arr is [“x”, “foo”, “bar”, “baz”, “y”] .

Calling genFn returns an object but it doesn’t run genFn .

The yield* keyword runs the generator function when it’s time for its items to be yielded.

yield* Considers End-of-Iteration Values

yield* ignores return values.

For example, if we have:

function* genFn() {
  yield 'foo';
  yield 'bar';
  return 'baz';
}

function* bar() {
  yield 'x';
  yield* genFn();
  yield 'y';
}

const gen = bar()
const arr = [...gen];

Then arr is [“x”, “foo”, “bar”, “y”] , yield* skipped over 'baz' since it’s returned instead of yield.

We can use yield* to recursively call generator functions.

So we can easily use it to return items from a tree structure.

For instance, we can create a Tree class with various branch nodes:

class Tree {
  constructor(value, left = null, center = null, right = null) {
    this.value = value;
    this.left = left;
    this.center = center;
    this.right = right;
  }

  *[Symbol.iterator]() {
    yield this.value;
    if (this.left) {
      yield* this.left;
    }

    if (this.center) {
      yield* this.center;
    }

    if (this.right) {
      yield* this.right;
    }
  }
}

left , center , and right are all generator functions created from the Tree class.

So we can write:

const tree = new Tree('a',
  new Tree('b',
    new Tree('c'),
    new Tree('d'),
    new Tree('e')),
  new Tree('f'));

for (const x of tree) {
  console.log(x);
}

And we get:

a
b
c
d
e
f

The value is the value for the tree node itself.

And we populate the nodes with the Tree constructor.

Generators as Observers

Generators can also be data observers.

We use it to send values with the next method.

The next method keeps returning values from then generator until the values run out.

For instance, if we have:

function* genFn() {
  yield 'foo';
  yield 'bar';
  return 'baz';
}

const gen = genFn();
console.log(gen.next());

And we get:

{value: "foo", done: false}

We can also call next with an argument to return that index of what’s yielded with the generator.

For example, we can write:

function* genFn() {
  console.log(yield);
  console.log(yield);
  console.log(yield);
}

const gen = genFn();
console.log(gen.next('a'));
console.log(gen.next('b'));
console.log(gen.next('c'));

We use the yield keyword to get the value passed in from the argument we passed into next .

The value return from the next method is:

{value: undefined, done: false}

value is undefined and done is false .

yield keyword without an operand gets the value from the next method.

Conclusion

The yield and yield* keyword have many uses.

yield can return values and also takes them from the next method.

Categories
Modern JavaScript

Best of Modern JavaScript — Regex y Flag

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at new JavaScript regex features.

Search and the y Flag

The String.prototype.search method ignores the y flag.

lastIndex isn’t changed by it.

For example, if we have:

const REGEX = /foo/y;

REGEX.lastIndex = 8;
const match = 'barfoobarfoo'.search(REGEX);
console.log(match.index);
console.log(REGEX.lastIndex);

Then match us undefined and lastIndex stays at 8.

We can only search from the beginning of the string with it.

The String.prototype.match works differently depending on whether the g flag is added with y .

If g isn’t, it works like exec .

If g is set, then it returns an array with string parts that’s matched or null .

For instance, if we write:

const REGEX = /a/;

REGEX.lastIndex = 7;
console.log('abab'.match(REGEX).index);
console.log(REGEX.lastIndex);

Then indx is 0, and lastIndex is 7.

It gets the first if no flag is added.

If we add the y flag:

const REGEX = /a/y;

REGEX.lastIndex = 2;
console.log('abab'.match(REGEX).index);
console.log(REGEX.lastIndex);

Then the match is only returned if the search starts from the lastIndex .

The index must be exact.

lastIndex will then be updated after the match is found.

If the g flag is set, then match will match all the substrings and put them in the array.

For example, we can write:

const REGEX = /a|b/g;

REGEX.lastIndex = 0;
console.log('abab'.match(REGEX));
console.log(REGEX.lastIndex);

And match returns [“a”, “b”, “a”, “b”] .

lastIndex is still 0.

lastIndex is ignored with the g flag enabled.

The result is the same with the gy flags added.

match and lastIndex all give us the same result.

Split Strings

String.prototype.split method lets us split a string.

The y flag will make the method’s behavior change.

For example, we can write:

console.log('abb'.split(/b/y))
console.log('bba'.split(/b/y))

The strings will be split with b as the separator.

So we get:

["a", "", ""]

and

["", "", "a"]

The y flag works regardless of the location of the flag.

We can also put the pattern in a capturing group:

console.log('abb'.split(/(b)/y))

Then we get:

["a", "b", "", "b", ""]

as the result.

Replace Strings

TheString.prototype.replace method lets us replace strings by matching them with a regex.

For instance, if we write:

const REGEX = /a/;
console.log('baba'.replace(REGEX, 'x'));

Then we get 'bxba’ logged.

It only logs the first entry that’s matched.

If the y flag is added, we also get at most one match.

But the match is always searched from the beginning of the string.

For example, if we have:

const REGEX = /a/y;
REGEX.lastIndex = 2;
console.log('baba'.replace(REGEX, 'x'));

Then we get 'baba’ logged.

If the lastIndex is beyond the first match, then it’ll be ignored even if there’s a match available.

With the g flag, the replace replaces all matches.

So, if we have:

const REGEX = /a/g;
REGEX.lastIndex = 2;
console.log('baba'.replace(REGEX, 'x'));

Then we get 'bxbx' logged.

If we combine the g and y flags:

const REGEX = /a/gy;
console.log('baba'.replace(REGEX, 'x'));

Then 'baba’ is returned from the replace method.

But if we have:

const REGEX = /a/gy;
console.log('aaba'.replace(REGEX, 'x'));

Then we get ‘xxba’ . This means that the first match must be at the beginning of the string for replace to work.

Conclusion

The y flag is a new flag introduced with ES6 which works differently with different methods.