Categories
Modern JavaScript

Best Features of ES2018 — Object Rest and Spread

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at the best features of ES2018.

Rest Operator on Objects

We can use destructuring with the rest operator in objects.

For instance, we can write:

const obj = {
  a: 1,
  b: 2,
  c: 3
};
const {
  a,
  ...rest
} = obj;

Then a is 1 and rest is:

{
  "b": 2,
  "c": 3
}

If we’re using object destructuring to handle named parameters, the rest operator lets us collect all the remaining parameters.

So if we write:

function foo({
  a,
  b,
  ...rest
}) {
  console.log(
    a,
    b,
    rest
  );
  return a + b;
}

foo({
  a: 1,
  b: 2,
  c: 3
})

Then a is 1, b is 2, and rest is {c: 3} .

We can use the rest operator for destructuring at most once and only at the end.

This makes sense since it’s the only way JavaScript engines know what hasn’t been assigned to a variable yet with destructuring.

So we can’t write:

const {...rest, a} = obj;

or:

const {foo, ...a, ...b} = obj;

They both will give us syntax errors.

We can nest the rest operator.

For instance, we can write:

const obj = {
  qux: {
    a: 1,
    b: 2,
    c: 3,
  },
  foo: 4,
  baz: 5,
  bar: 6,
};
const {
  qux: {
    a,
    ...rest1
  },
  ...rest2
} = obj;

Then a is 1, rest1 is {b: 2, c: 3} .

And rest2 is {foo: 4, baz: 5, bar: 6} .

Spread Operator in Object Literals

The spread operator in object literals is new to ES2018.

For instance, we can use it by writing:

const obj = {
  a: 1,
  b: 2
};

const obj2 = {
  ...obj,
  baz: 3
}

We used the spread operator to copy the properties of obj into a new object and assigned it to obj2 .

So obj2 is {a: 1, b: 2, baz: 3} .

The order matters since they’ll be populated in the order they’re spread.

So if we have:

const obj = {
  a: 1,
  b: 2
};

const obj2 = {
  baz: 3,
  ...obj
}

Then obj2 is {baz: 3, a: 1, b: 2} .

The 2 keys clash, then the later one overwrites the earlier ones.

So if we have:

const obj = {
  a: 1,
  baz: 2
};

const obj2 = {
  baz: 3,
  ...obj
}

Then we get:

{baz: 2, a: 1}

as the value of obj2 .

Uses of the Object Spread Operator

The object spread operator has a few use cases.

One of them is to clone an object.

For instance, we can write:

const obj = {
  a: 1,
  b: 2
};

const clone = {
  ...obj
};

Then a shallow copy of obj is made with the spread operator and so clone is:

{
  a: 1,
  b: 2
}

This is the same as using Object.assign to do the cloning;

const obj = {
  a: 1,
  b: 2
};

const clone = Object.assign({}, obj);

Spread is shorter and cleaner.

Conclusion

The object rest and spread operator are great new operators that are released with ES2018.

It’s clean and convenient.

Categories
Modern JavaScript

Best Features of ES2018 — New Regex Features

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at the best features of ES2018.

Regex Property Escapes

Unicode has properties, which are metadata describing it.

There are properties like Lowercase_Letter to describe lowercase letters, White_space to describe white spaces etc.

There’re several types of properties.

Enumerated property is a property value whose values are few and named.

General_Category is an enumerated property.

Close enumerated property ios an enumerated property whose set values is fixed.

Boolean property is a close enumerated property whose value is true or false .

Numeric properties have values that are real numbers.

String-valued property is a property whose values are strings.

Catalog property is an enumerated property that may be extended as Unicode changes.

Miscellaneous property is a property whose values aren’t any of the above.

There are various kinds of matching properties and property values.

The properties are loose matching, so General_Category is considered the same as GeneralCategory and other variants.

Unicode Property Escapes for Regex

We can use the \p characters to escape the Unicode properties.

This must be used with the /u flag to enable Unicode mode.

\p is the same as p without Unicode mode.

For instance, we can write:

const result = /^\p{White_Space}+$/u.test(' ')

and result would be true .

This means \p{White_Space} matches whitespace.

This is more descriptive than regular regex patterns.

We can also write:

const result = /^\p{Letter}+$/u.test('abc')

to match letters.

To match Greek letters, we write:

const result = /^\p{Script=Greek}+$/u.test('μ')

And we can match Latin letters with:

const result = /^\p{Script=Latin}+$/u.test('ç')

Long surrogate characters can also be matched:

const result = /^\p{Surrogate}+$/u.test('\\u{D83D}')

Lookbehind Assertions

A lookbehind assertion is a construct in a regex that specifies what the surroundings of the current location must look like.

For instance, we can write:

const RE_DOLLAR_PREFIX = /(?<=\$)\d+/g;

const result = '$123'.replace(RE_DOLLAR_PREFIX, '456');

We have the (?<=\$) group to look for digits with a dollar sign before it.

Then when we call replace to replace the number, we just replace the number.

We searched for something with $ and digits after it with that regex.

So result is '$456' .

This doesn’t work if the prefix should be part of the previous match.

We can also add a ! to add a negative lookbehind assertion.,

For instance, we can write:

const RE_DOLLAR_PREFIX = /(?<!\$)baz/g;

const result = '&baz'.replace(RE_DOLLAR_PREFIX, 'qux');

The regex looks for baz without a dollar sign before it.

So if we called replace as we did, we get '&qux’ returned since $ isn’t in the string.

s (dotAll) Flag for Regex

The dotAll dlag is a enahnance of the . flag in a regex.

The . in a regex doesn’t match line terminator characters.

So if we have:

/^.$/.test('\n')

We get false .

To match line terminator characters, we’ve to wite:

/^[^]$/.test('\\n')

to match everything except no character or:

/^\[\s\S]$/.test('\n')

to match space or non-whitespace.

There’re various kinds of line termination characters.

They include:

  • U+000A line feed (LF) (\n)
  • U+000D carriage return (CR) (\r)
  • U+2028 line separator
  • U+2029 paragraph separator

With ES2018, we can use the /s flag to match something that ends with a line terminator:

const result = /^.$/s.test('\\n')

So result should be true .

The long name is dotAll .

So we can write:

/./s.dotAll

and that returns true .

Conclusion

Regex property escapes, lookbehind assertions, and the dotAll flag new additions to JavaScript regexes that let us match various special cases.

Categories
Modern JavaScript

Best Features of ES2018- Async Iteration

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at the best features of ES2018.

Async Iteration

With ES6, JavaScript introduced the for-of loop.

It lets us synchronously iterate through iterable objects.

However, there’s no way to iterate asynchronously.

ES2018 introduced the async iteration feature to fill this gap.

Synchronous iteration works with any object that has the Symbol.iterator method, which is a generator method.

It returns each item with sequentially when the next next method is called.

The returned object has the value returned from the generator, which is the current entry that the iterator hits.

It also has the done property to indicate whether all th values have been returned.

For instance, if we have:

const iterable = ['foo', 'bar'];
const iterator = iterable[Symbol.iterator]();

Then when we call:

iterator.next()

the first time, we get:

{ value: 'foo', done: false }

Then when we call it again, we get;

{ value: 'b', done: false }

And when we call it one more time, we get:

{ value: undefined, done: true }

The iteration is synchronous, so we can’t iterate asynchronously.

We want to be able to call promises in an iterable object one by one.

This is solved with the for-await-of loop.

For instance, we can use it by writing:

async function main() {
  const arr = [
    Promise.resolve('foo'),
    Promise.resolve('bar'),
  ];
  for await (const x of arr) {
    console.log(x);
  }
}
main();

We have an array of promises that we iterate through.

They’ll be run one by one.

So we get:

foo
bar

logged in the console log.

The for-await-of loop also works with synchronous iterables.

So we can write:

async function main() {
  for await (const x of ['foo', 'bar']) {
    console.log(x);
  }
}
main();

And we get the same results logged.

Async Generators

Async generators let us create async iterable.

An async generator is just a generator function that returns promises.

For instance, we can create an async generator function by writing:

async function* createAsyncIterable(arr) {
  for (const elem of arr) {
    yield elem;
  }
}

A normal generator returns a generator object.

And each invocation of the next method returns an object with the properties value and done .

And an async generator returns a generator object.

Each next call returns a promise for an object with properties value and done .

When we call an async generator, the JavaScript engine will wait for the promise to be settled before calling the next one.

Every async generator has a queue with promises to be settled with yield or throw .

And when next is called, a promise is queued.

And unless the async generator is already running, it resumes and waits for the promise to be finished.

It can finish with yield , throw or return await .

Once it’s finished the promise is returned.

The result of a settled promise is delivered in an async manner.

Conclusion

The for-await-of loop lets us iterate through async generators by returning promises of each value sequentially.

Categories
Modern JavaScript

Best Features of ES2018 — Async Generators

Since 2015, JavaScript has improved immensely.

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

In this article, we’ll look at the best features of ES2018.

yield* in Async Generators

yield* in async generator works like how they work with normal generators.

If we have:

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

Then we can use it by writing:

(async function() {
  for await (const x of gen()) {
    console.log(x);
  }
})();

And we get:

foo
bar

logged.

Like with normal generators, the returned value is ignored by the for-of loop.

The operand of yield* can be any async iterable.

Sync iterables are automatically converted to async ones like with for-await-of.

Async Iteration Errors

In normal generators, next can throw exceptions.

With async generators, next can reject promises it returns.

For instance, we can write:

async function* asyncGen() {
  throw new Error('error');
}

to throw an error in an async generator.

Then we can catch the error by writing:

asyncGen().next()
  .catch(err => console.log(err));

We get the error object logged in the catch callback.

next return the rejected promise.

Different Between Async Function and Async Generator Function

An async function returns immediately with a promise.

The promise is fulfilled with return or rejected with throw .

So we can either write:

(async function () {
    return 'foo';
})()
.then(x => console.log(x));

or:

(async function() {
  throw new Error('error');
})()
.catch(x => console.log(x));

Async generators return immediately with an async iterable.

And each call of next returns a promise with yield x to fulfill the current promise with {value: x, done: false} .

It can also throw an error with the current promise with err .

So we can write:

async function* genFn() {
  yield 'foo';
}
const gen = genFn();
gen.next().then(x => console.log(x));

Then x is {value: “foo”, done: false} .

Turning an Async Iterable into an Array

We can turn an async variable into an array by looping through the async iterable and then pushing a value into a regular array.

For instance, we can write:

async function convertToArr(asyncIterable) {
  const result = [];
  const iterator = asyncIterable[Symbol.asyncIterator]();
  for await (const v of iterator) {
    result.push(v);
  }
  return result;
}

to create a function that takes an asyncIterable and then push the items into an array and return it.

It returns a promise that resolved to the result array.

Then we can use it by writing:

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

(async () => {
  const arr = await convertToArr(genFn());
  console.log(arr);
})()

We created an async generator and passes the returned result of it to our convertToArr function that we created earlier.

Since it returns a promise that resolves to an array, we’ll see the value when we log it.

Internal Properties of Async Generators

Async generators have 2 internal properties.

[[AsyncGeneratorState]] has the state the generator is currently in.

It can be "suspendedStart", "suspendedYield", "executing", "completed" or undefined .

It’s undefined if it’s not fully initialized.

[[AsyncGeneratorQueue]] holds the pending invocation of next , throw or return .

Each queue has 2 fields, which are [[Completion]] and [[Capability]] .

[[Completion]] has the parameter for next , throw , or return that leads to the entry to be enqueued.

next , throw , or return indicates the method that created the entry and determines what happens after dequeuing.

[[Capability]] is the capability of the promise.

Conclusion

yield *can be used with async generators to call another generator.

There are also big differences between async functions and async generators.

Categories
Modern JavaScript

Best Features of ES2017 — String and Object 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 the best features of ES2017.

Object.entries()

Object.entries() is an object static method introduced with ES2017.

It returns an array of key-value pairs of an object with each key-value pair in an array.

For instance, if we have:

const obj = {
  foo: 1,
  bar: 2
};

for (const [key, value] of Object.entries(obj)) {
  console.log(key, value);
}

Object.entries takes an object and returns the key-value pairs.

Then we loop through the key-value pairs by destructuring the key-value pair array.

Maps via Object.entries()

Since Object.entries returns an array of key-value pairs in an array and the Map constructor takes that array, we can convert an object into a map with it.

For instance, we can write:

const map = new Map(Object.entries({
  foo: 1,
  bar: 2,
}));
console.log(JSON.stringify([...map]));

Then we get:

[["foo",1],["bar",2]]

Object.values()

Object.values() returns an array of values from the object.

It also takes an object as its argument.

For instance, we can write:

const obj = {
  foo: 1,
  bar: 2
};

Object.values(obj)

Then we get [1, 2] returned.

Object.getOwnPropertyDescriptors()

The Object.getOwnPropertyDescriptors method lets us get the property descriptors of an object.

It only returns the descriptor of non-inherited properties.

For instance, we can write:

const obj = {
  [Symbol('foo')]: 100,
  get bar() {
    return 'abc'
  },
};
console.log(Object.getOwnPropertyDescriptors(obj));

Then we get:

{
  bar: {
    configurable: true
    enumerable: true
  },
  Symbol(foo): {
    configurable: true
    enumerable: true
    value: 100
    writable: true
  }
}

configurable indicates whether we can change the property descriptors.

enumerable indicates whether the property is listed with the for-in loop.

value is the property value.

And writable indicates whether the property value can be changed.

We can use it to copy properties into an object.

If we just copy the value, then we miss all the other property descriptors.

Instead, we can call Object.getOwnPropertyDescriptors to get the object.

So we can write:

const source = {
  foo: 1
};
const target = {};

Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
console.log(Object.getOwnPropertyDescriptor(target, 'foo'));

Then we get all the property descriptors of source copied to target .

And we can set the property descriptors of target.foo to check if everything has been copied over.

String Methods

ES2017 introduced new string method.

New methods include the padStart and padEnd methods.

padStart lets us pad a string to the given name by adding one or more characters repeated to the start of the string.

For instance, if we have:

'x'.padStart(5, 'yz')

then we get:

"yzyzx"

Likewise, there’s a padEnd method that pads a string to a given length with one or more characters repeated to the end of the string.

For instance, we can write:

'x'.padEnd(5, 'yz')

Then we get:

"xyzyz"

returned.

In each method, the first argument is the length, and the 2nd is the string that we want to pad our original string with.

Conclusion

ES2017 has some handy object and string methods we can use.