Categories
JavaScript Answers

New JavaScript Features Coming in ES2020 That You Can Use Now (Part 2)

Since 2015, the JavaScript is evolving fast with lots of new features coming out in each iteration. New versions of the JavaScript language specification have been updated yearly, with new language feature proposals being finalized faster than ever.

This means that new features are getting incorporated into modern browsers and other JavaScript run-time engines like Node.js at a pace that we haven’t seen before. In 2019, there are many new features that are in the ‘Stage 3’ phase, which means that it’s very close to being finalized and browsers are getting support for these features now.

If we want to use them for production code, we can use something like Babel to transpile them to older versions of JavaScript so they can be used in older browsers like Internet Explorer if needed.

In this article, we’ll look at static fields and methods in classes and using the await keyword at the top level in regular code and module exports.

Static Fields and Methods in Classes

Static fields in classes is a new feature that’s coming soon to browsers and Node.js. It lets us add static fields for classes that do not need an instance of the class to be created to be accessed. They can both be private or public. It’s a handy replacement for enums. For example, with the latest proposal, we can write a class with static variables like in the following example:

class Fruit {
  static orange = 'orange';
  static grape = 'grape';
  static banana = 'banana';
  static apple = 'apple';

  static #secretPear = 'pear';

}

console.log(Fruit.orange);
console.log(Fruit.grape);
console.log(Fruit.banana);
console.log(Fruit.apple);

The example above should output:

orange
grape
banana
apple

If we try to access secretPear from outside the class, like in the following code:

console.log(Fruit.#secretPear);

We’ll get ‘Uncaught SyntaxError: Private field ‘#secretPear’ must be declared in an enclosing class.’

The keyword static is already working with methods in the classes since ES6. For example, we can declare a static method within a class by using the static keyword like in the following code:

class ClassStatic {
  static staticMethod() {
    return 'Static method has been called.';
  }
}

console.log(ClassStatic.staticMethod());

We should get ‘Static method has been called’ from the console.log output. As we can see the staticMethod method was called without instantiating the class ClassStatic class just like we expected. Static methods can also be private, we just add a # sign before the name of the method to make it private. For example, we can write the following code:

class ClassStatic {
  static #privateStaticMethod(){
    return 'Static method has been called.';
  }

  static staticMethod() {
    return ClassStatic.#privateStaticMethod();
  }
}

console.log(ClassStatic.staticMethod());

In the code above, we called the privateStaticMethod from the staticMethod to get the value of the return value from privateStaticMethod in staticMethod. Then when we call staticMethod by running ClassStatic.staticMethod() then we get ‘Static method has been called.’ However, we can’t call privateStaticMethod directly from outside the class, so if we run something like:

class ClassStatic {
  static #privateStaticMethod(){
    return 'Static method has been called.';
  }

  static staticMethod() {
    return ClassStatic.#privateStaticMethod();
  }
}

console.log(ClassStatic.#privateStaticMethod());

Then we will get an error. Also, note that we aren’t using this to access the privateStaticMethod , we used the class name ClassStatic to do it. If we use this to call it, as in this.#privateStaticMethod() , we’ll get an error or undefined depending on the Babel version we’re using. The static keyword for variables is partially supported within browsers. Chromium browsers like Chrome and Opera support it. Also, Node.js supports this keyword. Other browsers do not support it yet, so we’ve to use Babel to convert it into older JavaScript code for it to run on other browsers.

Top Level await

Currently, the await keyword can only be used inside async functions, which are functions that return promises. If we use the await keyword outside of an async function, we’ll get an error like ‘Cannot use keyword ‘await’ outside an async function’ and our program won’t run. Now we can use the await keyword without it being inside an async function. For example, we can use it with the following code:

const promise1 = new Promise((resolve)=>{
  setTimeout(()=> resolve('promise1 resolved'), 1000);
})

const promise2 = new Promise((resolve)=>{
  setTimeout(()=> resolve('promise2 resolved'), 1000);
})

const val1 = await promise1;
console.log(val1);
const val2 =  await promise2;
console.log(val2);

Then when we run this code above in Chrome, we’ll get:

promise1 resolved
promise2 resolved

With top-level await, we can run asynchronous code however we like, and we don’t have to go back to using the long-winded then function chaining with callbacks passed into each then function call. This is very clean and we don’t have to wrap it inside an async IIFE (immediately invoked function expression) to run it if we don’t want to declare a named function.

Another good thing about top-level await is that we can export it directly. This means that when the module exports something that has the await keyword, it will wait for the promise being the await keyword to resolve before running anything. On the surface, it acts as if it’s imported synchronously as usual, but underneath, it actually waits for the promise in the export expression to resolve. For example, we can write:

const promise1 = new Promise((resolve)=>{
  setTimeout(()=> resolve('promise1 resolved'), 1000);
})
export const val1 = await promise1;

When we import module1.js in module2.js :

import { val1 } from './module1';
console.log(`val1);

Then we can see the value of val1 logged as if it’s running synchronously, waiting for promise1 to resolve.

Conclusion

Static fields in classes is a new feature that’s coming soon to browsers and Node.js. It lets us add static fields for classes that do not need an instance of the class to be created to be accessed. They can both be private or public. It’s a handy replacement for enums.

Static methods have existed since ES6 and so it can be used now and has widespread support. Using the await keyword at the top level in regular code and module exports is very handy once it’s a finalized feature. It lets us use await to write asynchronous code outside of async functions, which is much cleaner than using then with callbacks.

Also, it can let us export the resolved value of asynchronous code and then import it and it use it in other modules as if it was synchronous code.

Categories
Nodejs

Using Events in Node.js

The core feature of Node.js is asynchronous programming. This means that code in Node.js may not be executed sequentially. Therefore, data may not be determined in a fixed amount of time. This means that to get all the data we need, we have to pass data around the app when the data is obtained. This can be done by emitting, listening to, and handling events in a Node.js app. When an event with a given name is emitted, the event can listen to the listener, if the listener is specified to listen to the event with the name. Evenemitter functions are called synchronously. The event listener code is a callback function that takes a parameter for the data and handles it. Node.js has an EventEmitter class — it can be extended by a new class created to emit events that can be listened to by event listeners.

Define Event Emitters

Here’s a simple example of creating and using the EventEmitter class:

const EventEmitter = require('events');

class Emitter extends EventEmitter {}

const eventEmitter = new Emitter();
eventEmitter.on('event', () => {
  console.log('event emitted!');
});

eventEmitter.emit('event');

We should get ‘event emitted!’ in the console log. In the code above, we created the Emitter which extends the EventEmitter class, which has the emit function we called in the last line. The argument of the emit function is the name of the event, which we listen to in this block of code:

eventEmitter.on('event', () => {
  console.log('event emitted!');
});

The callback function, after the 'event' argument above, is the event handler function, that runs when the event is received.

In the code above, we emitted an event. However, it’s not very useful since we didn’t pass any data with the emitted event when we emit the event so it doesn’t do much. Therefore, we want to send data with the event so that we can pass data around so that we can do something useful in the event listener. To pass data when we emit an event, we can pass in extra arguments after the first argument, which is the event name. For instance, we can write the following code:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.on('event', (a, b) => {
  console.log(a, b);
});

eventEmitter.emit('event', 'a', 'b');

If we run the code above, we get ‘a’ and ‘b’ in the console.log statement insider the event handler callback function. As we see, we can pass in multiple arguments with the emit function to pass data into event handlers that subscribe to the event. After the first one, the arguments in the emit function call are all passed into the event listener’s callback function as parameters, so they can be accessed within the event listener callback function.

We can also access the event emitter object inside the event listener callback function. All we have to do is change the arrow function of the callback to a tradition function, as in this code:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.on('event', function(a, b){
  console.log(a, b);
  console.log(`Instance of EventEmitter: ${this instanceof EventEmitter}`);
  console.log(`Instance of Emitter: ${this instanceof Emitter}`);
});

eventEmitter.emit('event', 'a', 'b');

If we run the code above, we get this logged in the console.log statements inside the event listener callback function:

a b
Instance of EventEmitter: true
Instance of Emitter: true

On the other hand, if we have the following:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.on('event', (a, b) => {
  console.log(a, b);
  console.log(`Instance of EventEmitter: ${this instanceof EventEmitter}`);
  console.log(`Instance of Emitter: ${this instanceof Emitter}`);
});

eventEmitter.emit('event', 'a', 'b');

Then we get this logged in the console.log statements inside the event listener callback function.:

a b
Instance of EventEmitter: false
Instance of Emitter: false

This is because arrow functions do not change the this object inside it. However, tradition functions do change the content of the this object.

EventEmitter calls all listeners synchronously, in the order that they’re registered. This eliminates the chance of race conditions and other logic errors. To handle events asynchronously, we can use the setImmediate() or the process.nextTick() methods:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.on('event', (a, b) => {
  setImmediate(() => {
    console.log('event handled asychronously');
  });
});
eventEmitter.emit('event', 'a', 'b');

In the code above, we put the console.log inside a callback function of the setImmediate function, which will run the event handling code asynchronously instead of synchronously.

Events are handled every time they’re emitted. For example, if we have:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();
let x = 1;

eventEmitter.on('event', (a, b) => {
  console.log(x++);
});

for (let i = 0; i < 5; i++){
  eventEmitter.emit('event');
}

Since we emitted the ‘event’ event five times, we get this:

1
2
3
4
5

If we want to emit an event and handle it only the first time it’s emitted, then we use the eventEmitter.once() function, as in this code:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();
let x = 1;

eventEmitter.once('event', (a, b) => {
  console.log(x++);
});

for (let i = 0; i < 5; i++){
  eventEmitter.emit('event');
}

As expected, we only get this logged in the console.log statement of the event handler above:

1

Error Handling

If an error event is emitted in the case of errors, it’s treated as a special case within Node.js. If the EventEmitter doesn’t have at least one error event listener register and an error is emitted, the error is thrown, and the stack trace of the error will be printed, and the process will exit. For example, if we have the following code:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.emit('error', new Error('Error occured'));

Then we get something like this and the program exits:

Error [ERR_UNHANDLED_ERROR]: Unhandled error. (Error: Error occured
    at evalmachine.<anonymous>:5:28
    at Script.runInContext (vm.js:133:20)
    at Object.runInContext (vm.js:311:6)
    at evaluate (/run_dir/repl.js:133:14)
    at ReadStream.<anonymous> (/run_dir/repl.js:116:5)
    at ReadStream.emit (events.js:198:13)
    at addChunk (_stream_readable.js:288:12)
    at readableAddChunk (_stream_readable.js:269:11)
    at ReadStream.Readable.push (_stream_readable.js:224:10)
    at lazyFs.read (internal/fs/streams.js:181:12))
    at Emitter.emit (events.js:187:17)
    at evalmachine.<anonymous>:5:14
    at Script.runInContext (vm.js:133:20)
    at Object.runInContext (vm.js:311:6)
    at evaluate (/run_dir/repl.js:133:14)
    at ReadStream.<anonymous> (/run_dir/repl.js:116:5)
    at ReadStream.emit (events.js:198:13)
    at addChunk (_stream_readable.js:288:12)
    at readableAddChunk (_stream_readable.js:269:11)
    at ReadStream.Readable.push (_stream_readable.js:224:10)

To prevent the Node.js program from crashing, we can listen to the error event with a new event listener and handle the error gracefully in the error event handler. For example, we can write:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.on('error', (error) => {
  console.log('Error occurred');
});

eventEmitter.emit('error', new Error('Error occurred'));

Then we get “error occurred” logged. We can also get the error content with the error parameter of the event handler callback function. If we log it, as in this code:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();

eventEmitter.on('error', (error) => {
  console.log(error);
});

eventEmitter.emit('error', new Error('Error occurred'));

We will get something like this:

Error: Error occurred
    at evalmachine.<anonymous>:7:28
    at Script.runInContext (vm.js:133:20)
    at Object.runInContext (vm.js:311:6)
    at evaluate (/run_dir/repl.js:133:14)
    at ReadStream.<anonymous> (/run_dir/repl.js:116:5)
    at ReadStream.emit (events.js:198:13)
    at addChunk (_stream_readable.js:288:12)
    at readableAddChunk (_stream_readable.js:269:11)
    at ReadStream.Readable.push (_stream_readable.js:224:10)
    at lazyFs.read (internal/fs/streams.js:181:12)

More Ways to Deal with Events

Node.js will emit one special event without writing any code to emit the event: The newListener . The newListener event is emitted before a listener is added to the internal array of listeners. For example, if we have the following code:

const EventEmitter = require('events');
class Emitter extends EventEmitter {}
const eventEmitter = new Emitter();
eventEmitter.on('newListener', (`event, listener`) => {
console.log(`event`);
});

Then we get something like this logged:

Emitter {
  _events: [Object: null prototype] { newListener: [Function] },
  _eventsCount: 1,
  _maxListeners: undefined }

This happens even when no events are emitted. Whatever is in the handler will be run before the code in event handlers for any other events.

The removeListener function can be used to stop event listener functions from listening to events. This takes two arguments: The first is a string that represents the event name, the second is the function that you want to stop using to listen to events. For example, if we want to stop listening to the “event” event with our listener function, then we can write this:

const EventEmitter = require('events');
class Emitter extends EventEmitter { }
const eventEmitter = new Emitter();

const listener = () => {
  console.log('listening');
}

eventEmitter.on('event', listener)

setInterval(() => {
  eventEmitter.emit('event');
}, 300);

setTimeout(() => {
  console.log("removing");
  eventEmitter.removeListener('event', listener);
}, 2000);

Then we get something like this in the output:

Timeout {
  _called: false,
  _idleTimeout: 2000,
  _idlePrev: [TimersList],
  _idleNext: [TimersList],
  _idleStart: 1341,
  _onTimeout: [Function],
  _timerArgs: undefined,
  _repeat: null,
  _destroyed: false,
  [Symbol(unrefed)]: false,
  [Symbol(asyncId)]: 10,
  [Symbol(triggerId)]: 7 }listening
listening
listening
listening
listening
listening
removing

The event emitter emits the “event” event in the code above once every 300 milliseconds. This is listened to by the listener function, until it’s been prevented from listening again by calling the removeListener function with the “event” as the event name the listener event listener function in the callback of the setTimeout function.

Multiple event listeners can register for a single event. By default, the limit for the maximum number of event listeners is ten. We can change this with the defaultMxListeners function in the EventEmitter class. We can set it to any positive number. If it’s not a positive number, then a TypeError is thrown. If more listeners than the limit are registered then a warning will be output. For example, if we run the following code to register 11 event listeners for the “event” event:

const EventEmitter = require('events');
class Emitter extends EventEmitter { }
const eventEmitter = new Emitter();

const listener = () => {
  console.log('listening');
}

for (i = 1; i <= 11; i++){
  eventEmitter.on('event', listener);
}

eventEmitter.emit('event');

When we run the code above, we get this:

listening
listening
listening
listening
listening
listening
listening
listening
listening
listening
listening
(node:345) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit

However, if we call setMaxListeners to set it to getMaxListeners() + 1, which is 11 listeners, as seen in the following code:

const EventEmitter = require('events');
class Emitter extends EventEmitter { }
const eventEmitter = new Emitter();
eventEmitter.setMaxListeners(eventEmitter.getMaxListeners() + 1);

const listener = () => {
  console.log('listening');
}

for (i = 1; i <= 11; i++){
  eventEmitter.on('event', listener);
}

eventEmitter.emit('event');

Then we get the following logged:

listening
listening
listening
listening
listening
listening
listening
listening
listening
listening
listening

How to attach and remove event listeners

To get the names of the events that are listened to, we can use the eventNames function. The function takes no arguments and returns an array of event identifiers, which may include strings or symbols.

For example, we can use it as in the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
eventEmitter.on('event1', () => {});
eventEmitter.on('event2', () => {});

const sym = Symbol('event3');
eventEmitter.on(sym, () => {});

console.log(eventEmitter.eventNames());

When we run the code above, we get the following logged:

[ 'event1', 'event2', Symbol(event3) ]

To get the maximum number of listeners that can be attached to a single event, we can use the getMaxListeners function. It takes no arguments and returns the current maximum number of listeners that can be attached to one event.

The maximum number of event listeners that can be attached to the event can be set by the setMaxListeners function and the default value is 10. For example, we can use it as in the following example:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
console.log(eventEmitter.getMaxListeners());

We can use the listenerCount function to get the number of event listeners attached to an event. It takes one string or symbol argument for the event name and returns an integer with the number of event listeners attached to the event with the event name passed into the argument.

For example, we can use it as in the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();

for (let i = 1; i <= 5; i++) {
  eventEmitter.on('event', () => { });
}

console.log(eventEmitter.listenerCount('event'));

If the code above is run, we get 5 logged as we attached five event listeners to the event event.

To get the event listeners functions attached to a given event, we can use the listeners function. It takes a string or symbol with the event name and returns an array of event listener functions.

It returns a copy of the listeners rather than the original ones. For instance, we can use it as in the following example:

[ '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)' ]

To attach an event listener to the beginning of the array listeners for a given event, we can use the prependListener function.

The function takes the event name as the first argument and the event listener function as the second argument. For example, we can write the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();

for (let i = 1; i <= 5; i++) {
  eventEmitter.on('event', () => console.log(`Listener ${i} for 'event' event invoked`));
}

eventEmitter.prependListener('event', () => console.log('Prepended listener invoked'))

console.log(eventEmitter.listeners('event').map(f => f.toString()));

If we run the code above, we get the following logged:

[ '() => console.log('Prepended listener invoked')',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)' ]

As we can see, the prepended event listener is in the first slot of the array. If we add eventEmitter.emit('event') to emit the event event, as in the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();

for (let i = 1; i <= 5; i++) {
  eventEmitter.on('event', () => console.log(`Listener ${i} for 'event' event invoked`));
}

eventEmitter.prependListener('event', () => console.log('Prepended listener invoked'))

console.log(eventEmitter.listeners('event').map(f => f.toString()));

Then, we get the following logged with the console.log statements:

[ '() => console.log('Prepended listener invoked')',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)' ]
Prepended listener invoked
Listener 1 for 'event' event invoked
Listener 2 for 'event' event invoked
Listener 3 for 'event' event invoked
Listener 4 for 'event' event invoked
Listener 5 for 'event' event invoked

We also see that the listener that we prepended to the event listener array is invoked first since the listeners are handled in the same order as they’re added into the array.

If we want to prepend an event listener that only handles the emitted event once, we can use the prependOnceListener instead. For example, we can use it as in the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
for (let i = 1; i <= 5; i++) {
  eventEmitter.on('event', () => console.log(`Listener ${i} for 'event' event invoked`));
}
eventEmitter.prependOnceListener('event', () => console.log('Prepended once listener invoked'))

eventEmitter.emit('event');
eventEmitter.emit('event');

If we run the code above, we can see the following logged in the console.log statements:

Prepended once listener invoked
Listener 1 for 'event' event invoked
Listener 2 for 'event' event invoked
Listener 3 for 'event' event invoked
Listener 4 for 'event' event invoked
Listener 5 for 'event' event invoked
Listener 1 for 'event' event invoked
Listener 2 for 'event' event invoked
Listener 3 for 'event' event invoked
Listener 4 for 'event' event invoked
Listener 5 for 'event' event invoked

As we can see, the listener that we prepended with the prependOnceListener only runs once. This is what we expect if we attach an event listener with the prependOnceListener function.

If we want to remove all event listeners for a given event, we can use the removeAllListeners function. It takes a string or symbol argument for the event identifier.

Note that it’s bad practice to remove event listeners that are attached elsewhere in the code when the EventEmitter is created in some other component of your program. It returns a reference to the EventEmitter so that the calls can be chained.

For instance, we can write the following code to remove all event listeners for the event event:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();
for (let i = 1; i <= 5; i++) {
  eventEmitter.on('event', () => console.log(`Listener ${i} for 'event' event invoked`));
}
eventEmitter.prependOnceListener('event', () => console.log('Prepended once listener invoked'))

console.log('Before remove all listenersn', eventEmitter.listeners('event').map(f => f.toString()));

eventEmitter.removeAllListeners('event');

console.log('After remove all listenersn', eventEmitter.listeners('event'));

When the code above is run, we get the following logged:

Before remove all listeners
 [ '() => console.log('Prepended once listener invoked')',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)',
  '() => console.log(`Listener ${i} for 'event' event invoked`)' ]
After remove all listeners
 []

To remove a single event listener for a given event, we can use the removeListener function. It takes an argument with the string or symbol for an event identifier, and a reference to the listener that’s attached to the given event.

It returns a reference to the EventEmitter so that the calls can be chained. We can use it as in the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();

const listener1 = () => console.log('listener1 invoked');
const listener2 = () => console.log('listener2 invoked');

eventEmitter.on('event', listener1);
eventEmitter.on('event', listener2);

console.log('Before remove listenersn', eventEmitter.listeners('event').length);

eventEmitter
  .removeListener('event', listener1)
  .removeListener('event', listener2);

console.log('After remove listenersn', eventEmitter.listeners('event').length);

When we run the code above, we get the following logged with the console.log statements:

Before remove listeners
 2
After remove listeners
 0

As we can see, the removeListener can be chained and lets us remove the listeners one at a time. The listeners that were removed are no longer listening to the events.

If a single listener is attached to an event multiple times, then it might be called the number of times that the event listener is attached to remove all the event listeners.

If the removeListener function is called once in this scenario, it will remove the most recently added event listener. The event listeners array for the given event will be updated when the event listener is removed.

To get an array of event listeners for an event including the wrappers, we can use the rawListeners function. For example, we can use it as the following code:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();

const listener1 = () => console.log('listener1 invoked');
const listener2 = () => console.log('listener2 invoked');

eventEmitter.once('event', listener1);
eventEmitter.on('event', listener2);

const rawListeners = eventEmitter.rawListeners('event');
rawListeners[0].listener();

If we run the code above, we see that listener1 invoked is logged. This is because that the eventEmitter.once wraps the listener function that’s passed into it.

This is returned along with the actual event listener. Therefore, we can run the actual event listener function by running rawListeners[0].listener();.

This doesn’t work with the second listener because there’s no wrapper to wrap the listener function when we call eventEmitter.on to attach a listener.

An important feature of Node.js is asynchronous programming. This means that code in Node.js may not be executed sequentially. Therefore, data may not be determined in a fixed amount of time.

This means that to get all the data we need, we have to pass data around the app when the data is obtained. We can emit events and handle them within a Node.js app.

When an event with a given name is emitted, the event can listen to the listener if the listener is specified to listen to the event with the name. Event emitter functions are called synchronously.

The event listener code is a callback function that takes a parameter for the data and handles it. Node.js has an EventEmitter class that can be extended by a new class that we create to emit events that can be listened to by event listeners.

With the EventEmitter class, we can add and remove event listeners with built-in functions. We can remove all of them at the same time or one at a time.

However, we shouldn’t remove listeners from event emitters that aren’t written by us as it causes confusion to other people that are working on the code since other programmers do not expect built-in listeners from other modules to be removed.

Categories
JavaScript

Handy Tips for Working with Numbers in JavaScript

JavaScript, like any other programming language, has many handy tricks that let us write our programs more easily. In this article, we will look at how to do different things that involve numbers, like generating a number array, generating random numbers, and shuffling numbers.

Generating a Number Array within a Range

To generate a number from a minimum to a maximum number, we can do it in a few ways. If we want each entry to increment by 1, we can use the Array.from method. The Array.from method takes an object which has the length property.

To generate a number within a range, like from 1 to 10, we can make a constant for the maximum number and another one for the minimum number. Then the length would be the maximum minus the minimum plus one.

We need to add one since the maximum minus the minimum is one less than the length we want. The second argument of the Array.from method is a function that lets us map the values to the ones we want.

The first parameter is the value of the original array since we didn’t pass in an array into the first argument, this parameter isn’t useful for us.

The second argument is the index of the array, which will range from 0 to the length which we specified as the length property minus 1. So if we want to generate an array of numbers from 1 to 10, we can write:

const max = 10;
const min = 1;
const arr = Array.from({
  length: max - min + 1
}, (v, i) => min + i);
console.log(arr)

In the code above, we specified the length property of the array that we’ll generate, which is max — min + 1 or 10 – 1+1, which is 10. That’s the length we want. In the second argument, we have a function that maps the index i into min + i, which is will be 1 + 0 , 1 + 1 , 1 + 2 , …, up to 1 + 9 . Then if we run the console.log statement in the last line of the code above, we get:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

It’s easy to generate different kinds of number arrays by adjusting the function in the second parameter since it maps to any number we want. To generate the first n odd numbers, we can write:

const arr = Array.from({
  length: 10
}, (v, i) => 2 * i + 1);
console.log(arr)

In the code above, we have a function that generates odd numbers from a formula. 2 * i + 1 always generates an odd number since 2 times any integer is an event number, so if we add 1 to it, then it will become an odd number. It’s useful for any number sequence that has a pattern that can be put into a formula or be expressed in terms of conditionals or other flow control statements.

Generate Random Numbers

The JavaScript standard library has a Math object, which has a random method to generate random numbers between 0 and 1. It takes no arguments.

By itself, it’s of limited use since it can only generate numbers between 0 and 1, but we can easily use it to generate random numbers in any range.

For example, if we want to generate a number between a minimum and a maximum number, we can write the following code:

const min = 1;
const max = 100;
const num = Math.random() * (max - min) + min;
console.log(num);

In the code above, we have a formula that has the min number as the minimum number, then we add Math.random() * (max — min) to min. max-min would be positive since max is bigger than min and Math.random() returns a number between 0 and 1 so it would either be 0 or positive.

If max is the same as min, then we get min as the result of num, which is the lowest number we’ll accept, and if Math.random() is 0 we get the same thing.

If Math.random() is 1, we get max — min + min which is the same as max. This means that the formula would never be outside of the number range between min and max, which is what we want. The formula above will get us any floating-point number. If want integer results only, we can write:

let min = 1;
let max = 10;
min = Math.ceil(min);
max = Math.floor(max);
const num = Math.floor(Math.random() * (max - min)) + min;
console.log(num);

In the code above, we rounded down our result to the nearest integer with the Math.floor(Math.random() * (max — min)) method call. This makes sure the result generated would be between 1 inclusive and 10 exclusive.

Generate an Array of Random Numbers

We can easily expand the code above to generate an array of random numbers. Using the Array.from method that we used above, we can create the array like in the following code:

const arr = Array.from({
  length: 10
}, (v, i) => {
  const min = 1;
  const max = 10;
  return Math.random() * (max - min) + min
});
console.log(arr)

In the second argument of the Array.from method call, we modified the function for mapping the values to the number generator function with the code inside it as the same code that we used to generate random numbers.

Again, we can specify the length property of the object in the first argument to the length of the array that we want. If we run the code above, the console.log output from the statement on the last line should log something like:

[2.09822597784169, 9.485219511793478, 4.905513590655689, 5.428402066955793, 5.874266428138649, 6.288415362686418, 2.768483595321933, 9.849179787675595, 1.1291148797430082, 6.725810669834928]

Also, we can use the integer generator code that we have before in the same manner, like in the code below:

const arr = Array.from({
  length: 10
}, (v, i) => {
  let min = 1;
  let max = 10;
  min = Math.ceil(min);
  max = Math.floor(max);
  return num = Math.floor(Math.random() * (max - min)) + min;
});
console.log(arr)

We have the same code that we used to generate integers as before, except that we used it to generate an array of random integers. If we run the code above, we should get something like the following:

[8, 3, 5, 4, 4, 9, 9, 1, 1, 1]

from the console.log statement above.

Shuffling an Array of Numbers

With the sort method built into the JavaScript array objects, we can use it easily to sort arrays. The sort method takes a function that lets us compare 2 entries in the array to let us set the sort order between 2 elements based on the conditions that we specify.

The function that we pass into the sort method takes 2 parameters, the first is an element which we call a, and the second is an element which we call b, and we compare a and b to determine which should be sorted above the other in the array. The sort function expects a number to be returned by the callback.

If the value that’s returned by our function is negative, then a will be in an array index that’s lower than b. That is, a comes before b. If the number is positive, then b will come before a. If the function returns 0, then a and b will stay in their original position.

If we want to shuffle the array, we can just return a random number from it — the value of a or b will not have an impact on the sort. We can once again use the Math.random() method to help us generate a random return value for the function we pass into the sort method. We can write the following code to sort each entry in random order (shuffle the array):

let nums = [49, -151, 88, -133, -164, 78, 117, -25, 76, -40];
nums = nums.sort(() => {
  return Math.random() - 0.5
});
console.log(nums);

In the code above, our sort method has a function which generates a random number between -0.5 and 0.5, which means there’s a chance that each element either stays in place or gets shuffled, according to what the sort method will do depending on the return value.

The Math.random() only returns numbers between 0 and 1. However, we can easily make it more useful by extending it in the ways that we did above. We can specify a range between a minimum and a maximum number and Math.random() * (max — min) + min to generate a number between a range. Also, we can pass it into the callback function of the sort method by returning Math.random() — 0.5 to generate a number between -0.5 and 0.5, which means that it can be shuffled (or not) depending on the sign of the generated number and whether it’s 0 or not.

Categories
TypeScript

Cool New Features Released in TypeScript 3.7

With the release of TypeScript 3.7, some great new features that are included from ES2020 that are now part of TypeScript. The new features include things like optional chaining, nullish coalescing, check for uncalled functions, and more.

In this article, we’ll look at some of them in more detail.

Optional Chaining

Optional chaining is a feature that lets us get a deeply nested property of an object without worrying if any of the properties are null or undefined.

We can use the ?. operator to get the properties of an object instead of using the usual dot operator. For example, instead of writing:

let x = a.b.c.d;

We write:

let x = a?.b?.c?.d;

With the optional chaining operator, we don’t have to worry about properties b or c being null or undefined, which saves us from writing lots of code to check is these properties exist.

If any of the intermediate properties are null or undefined, then undefined is returned instead of crashing the app with errors.

This means that we no longer have to write something like:

let x = a && a.b && a.b.c && a.b.c.d;

To get the d property from the a object.

One thing to be careful is that if strictNullChecks flag is on, then we’ll get errors if we operate on an expression with the optional chaining operator inside a function with a parameter that has optional parameter as the operand.

For example, if we write:

let a = { b: { c: { d: 100 } } };
const divide = (a?: { b: { c: { d: 100 } } }) => {
  return a?.b?.c?.d / 100;
}

Then we get the error ‘Object is possibly ‘undefined’.(2532)’.

Nullish Coalescing

The nullish coalescing operator lets us assign a default value to something when something is null or undefined.

An expression with the nullish coalescing looks like:

let x = foo ?? bar;

Where the ?? is the nullish coalescing operator.

For example, we can use it as follows:

let x = null;
let y = x ?? 0;
console.log(y); // 0

Then we get 0 as the value of y .

This is handy because the alternative was to use the || operator for assigning default values. Any falsy value on the left operand would cause the default value on the right operand to be assigned, which we might not always want.

The nullish coalescing operator only assigns the default value when the left operand is null or undefined.

For example:

let x = 0;
let y = x || 0.5;

In the code above, 0 is a valid value that can be assigned to y, but we still assign the default value of 0.5 to it because 0 is falsy, which we don’t want.

Assertion Functions

TypeScript 3.7 comes with the asserts keyword which let us write our own functions to run some checks on our data and throws an error if the check fails.

For example, we can write the following to check if a parameter passed into our assertion function is a number:

function assertIsNumber(x: any): asserts x is number {
    if (typeof x === 'number') {
        throw new Error('x is not a number');
    }
}
assertIsNumber('1');
assertIsNumber(1);

When we run the code above, we should get ‘Uncaught Error: x is not a number’.

In the code above, the asserts keyword checks whatever condition comes after it.

It’s a function that returns void which means that it doesn’t return anything. It can only throw errors if it doesn’t meet the condition we define.

Better Support for Functions that Return the Never Type

With TypeScript 3.7, now the TypeScript compiler recognizes functions that returns the never type is run in a function that returns some other type.

For example, before TypeScript 3.7, we have to write the following to avoid an error:

const neverFn = (): never => {
    throw new Error();
};

const foo = (x: string | number): number => {
    if (typeof x === 'string') {
        return +x;
    }
    else if (typeof x === 'number') {
        return x;
    }
    return neverFn();
}

The code above would get us the error “Function lacks ending return statement and return type does not include ‘undefined’.” Because we did add return before the neverFn() function call.

TypeScript versions earlier than 3.7 don’t recognize the never function as a valid path because it doesn’t allow a path in the code that returns undefined if a return type is specified.

Now removing the return in return neverFn(); will work if TypeScript 3.7 is used to compile the code.

Allowing Some Recursive Type Aliases

Type aliases that aren’t assigned to itself are now allowed with TypeScript 3.7. For example, the following is still not allowed:

type Bar = Bar;

since it just replaces the Bar type with itself forever.

If we try to compile the code above, we would get the error “Type alias ‘Bar’ circularly references itself. (2456)“.

However, now we can write something like:

interface Foo { };
interface Baz { };
type Bar = Foo | Baz | Bar[];

This is because the Bar[] type isn’t directly replacing Bar , so this type of recursion is allowed.

Generating Declaration Files when AllowJs Flag is On

Before TypeScript 3.7, we can’t generate declaration files when the --allowJs flag is on, so TypeScript code that is compiled with JavaScript can’t have any declaration files generated.

This means that type checking can’t be done with JavaScript files that are being compiled, even if they have JSDoc declarations.

With this change, now type checking can be done with those JavaScript files.

Now we can write libraries with JSDoc annotated JavaScript and support TypeScript users with the same code.

The TypeScript compiler since 3.7 will infer the types of the JavaScript code from the JSDoc comments.

Uncalled Function Checks

Forgetting to call a function by omitting the parentheses is a problem that sometimes causes bugs. For example, if we write the following:

const foo = () => { };

const bar = () => {
    if (foo) {
        return true;
    }
}

We’ll get the error “This condition will always return true since the function is always defined. Did you mean to call it instead?(2774)” when we write the code above and try to compile it with the TypeScript 3.7 compiler.

This code wouldn’t give any errors before version 3.7.

Conclusion

As we can see, TypeScript 3.7 gives us a lot of useful new features that aren’t available before. Optional chaining and nullish coaslecing operators are handy for avoiding null or undefined errors.

Recognizing function calls that return the never type is also handy for writing code with paths that don’t always return.

Having recursive type aliases help with combining some kinds of types into one alias.

For developers that write libraries, TypeScript 3.7 can infer types from JavaScript files that are compiled with the TypeScript compiler by checking the JSDoc comments.

Finally, uncalled functions are now checked by the TypeScript compiler if they’re written like we’re trying to access it as a property.

Categories
JavaScript

Manipulate URLs and Query String with JavaScript

Like any other programming language, JavaScript has many handy tricks that let us write our programs more easily. In this article, we will look at how to manipulate URLs with the URL object with various properties and manipulate query strings with the URLSearchParams object.

Manipulate URLs

With the URL object, we can pass in a URL string and extract and set various parts of a URL and create a new URL. We can create a URL object by using the constructor. The constructor takes up to 2 arguments. We can have one argument being the complete URL string, or we can pass in a relative URL string as the first argument and the hostname as the second argument. For example, we can either write:

new URL('http://medium.com');

or

new URL('/@hohanga', 'http://medium.com');

In the last part, we looked at the hash and host property by looking at the following examples:

const url = new URL('http://example.com/#hash');
console.log(url.hash);
url.hash = 'newHash';
console.log(url.toString());

If we run the code, we can see that the first console.log statement logs '#hash'. Then we assigned the value 'newHash' to the url’s hash property. Then when we run the toString() method on the url object and the run the console.log method on the value returned by toString(), we get '[http://example.com/#newHash](http://example.com/#newHash)' which is the new value of the URL with the new hash.

const url = new URL('http://example.com/#hash');
console.log(url.host);
url.host = 'newExample.com';
console.log(url.toString());

When we run the code, we can see that the first console.log statement logs '#hash'. Then we assigned the value 'example.com' to the url’s hash property. When we run the toString() method on the url object and use the console.log method on the value returned by toString() , we get ‘[http://newexample.com/#hash](http://newexample.com/#hash)’ which is the new value of the URL with the new hash.

In this part of the series, we will look at the other parts of the URL object. To get and set the whole URL we can use the href property. For example, we can write the following code:

const url = new URL('http://example.com/href1/href2');
console.log(url.href);
url.href = 'http://example.com/newHref1/newHref2';
console.log(url.toString());

In the code above, the first console.log statement would get us the whole URL, that is, ‘[http://example.com/href1/href2'](http://example.com/href1/href2%27) . Then once we set a new value to the href property as we did on the third line, then we should get a string with a new URL when we run the console.log statement at the bottom. Therefore, we should get ‘[http://example.com/newHref1/newHref2](http://example.com/newHref1/newHref2)’ from the console.log statement on the last line.

We can use the username and password properties to set the username and password portion of the URL. The username is the part of the URL that’s right after the protocol and before the colon. The password portion of the URL is between the colon and the ampersand sign. For example, https://username:password@example.com. To get and set the username portion of the URL, we can use the username property. For example, we can write:

const url = new URL('https://username:password@example.com');
console.log(url.username);
url.username = 'newUsername';
console.log(url.toString());

The first console.log should output 'username' which is the username of the original URL. Then we set the username property to 'newUsername' so that when returning the URL object converted to a string, we get that we have ‘[https://newUsername:password@example.com/](https://newUsername:password@example.com/)’ .

Likewise, we can do the same thing with the password property. For example, we can write:

const url = new URL('https://username:password@example.com');
console.log(url.password);
url.password = 'newPassword';
console.log(url.toString());

The first console.log should output 'password' which is the username of the original URL. Then we set the password property to 'newPassword' so that when returning the URL object converted to a string, we get that we have ‘[https://newUsername:password@example.com/](https://username:newPassword@example.com/)’ .

With the pathname the property, we can set the part of the URL after the host part — that is, the relative URL after the first slash. For instance, we can write the following code:

const url = new URL('https://example.com/path1/path2');
console.log(url.pathname);
url.pathname = '/newPath1/newPath2';
console.log(url.toString());

The first console.log should output '/path1/path2' , which is the username of the original URL. Then we set the pathname property to '/newPath1/newPath2' so that when returning the URL object converted to a string, we get that we have ‘[https://newUsername:password@example.com/](https://example.com/newPath1/newPath2)’ .

There’s also the port and protocol properties to manipulate the port portion and the protocol portion of the URL respectively. If we have a URL like https://example.com:8888 then 8888 would be the port number. For HTTP the default it 80 and for HTTPS the default port is 443, so if it’s not specified then the default port is used. We can get and set them like all the other properties mentioned above. For example, we can write:

const url = new URL('https://example.com:8888');
console.log(url.port);
url.port = '3333';
console.log(url.toString());

console.log(url.protocol);
url.protocol = 'http';
console.log(url.toString());

Then we should get something like:

8888
https://example.com:3333/
https:
http://example.com:3333/

from the 4 console.log statements.

Manipulate Query Strings

With the URLSearchParams object, we can get the query string from a URL string. For example, if we have had the URL https://example.com/?key1=value1&key2=value2 then we can use the URL’s search property to get the query string from the URL as we do with the following code:

const url = new URL('[https://example.com/?key1=value1&key2=value2'](https://example.com/?key1=value1&key2=value2%27));
console.log(url.search);

Then we get back ?key1=value1&key2=value2 from the console.log output. Note that the search property only gets the whole query string. There’s nothing in the URL object that lets us manipulate the query string. This is where the URLSearchParams object comes in. We can get it from the URL object by using its searchParams property. For example, if we have the same URL as the earlier example, then we can write:

const url = new URL('https://example.com/?key1=value1&key2=value2');
console.log(url.`searchParams`);

With the URLSearchParams object, we can use its methods to get and set various parts of the query string. We can use the get method to get the value of a search parameter given a key for it. For instance, we can write:

const url = new URL('https://example.com/?key1=value1&key2=value2');
console.log(url.searchParams.get('key1'));
console.log(url.searchParams.get('key2'));

Then we get back the following:

value1
value2

These are the values of the search parameters that we expect. There’s also a getAll method to get the values of all search parameters with the given key. If we write the following code:

const url = new URL('https://example.com/?key1=value1&key1=value2&key2=value3');
console.log(url.searchParams.getAll('key1'));

So if we have the URL with the query string above, which has 2 values set for the key1 search parameter, then when we call url.searchParams.getAll(‘key1’) we get back [“value1”, “value2”] .

There’s also a keys method to get all the keys from the query string. Again, if we have URL the example above, we can get all the keys from the query string with the key method like we do below:

const url = new URL('[ttps://example.com/?key1=value1&key1=value2&key2=value3');
console.log([...url.searchParams.keys()]);

The keys method returns an iterator object, so if we want to get an array, we have to use the spread operator as we did above. Once we did that, we get back [“key1”, “key1”, “key2”] .

To check if a key exists in the search parameters of our query string, we can use the has method. It’s very straightforward to use, we just have to pass in the string with the key name we want to look for:

const url = new URL('https://example.com/?key1=value1&key1=value2&key2=value3');
console.log(url.searchParams.has('key1'));
console.log(url.searchParams.has('key4'));

If we run the code above, we should get:

true
false

The key method returns a boolean true if the value for the given key exists and false otherwise. To add a search parameter to the query string, we can use the append method. We can append as many search parameters with the same keys as many times as we like. For example, we can call the append method like in the following code:

const url = new URL('https://example.com/?key1=value1&key1=value2&key2=value3');
url.searchParams.append('key4', 'value4');
url.searchParams.append('key4', 'value5');
url.searchParams.append('key5', 'value5');
console.log(url.toString());

Then the console.log at the end should output https://example.com/?key1=value1&key1=value2&key2=value3&key4=value4&key4=value5&key5=value5.

To edit a key that’s in a URL, we can use the URLSearchParams ‘ set method. It takes in the string of the key of the search parameter as the first argument the string of the value you want to set for the search parameter as the second argument. For example, we call it like in the following code:

const url = new URL('https://example.com/?key1=value1&key1=value2&key2=value3');
url.searchParams.set('key1', 'newValue');
console.log(url.toString());

Then we get back https://example.com/?key1=newValue&key2=value3. The set method will remove duplicate search parameters with the same keys when it runs and set the new value to the one that’s left. So the second instance of the key1 search parameter is removed and newValue is set as the value of the first instance of the key1 search parameter.

Finally, to remove a search parameter with the given key string, we can call the delete method. For example, we can use it as in the following code:

const url = new URL('https://example.com/?key1=value1&key1=value2&key2=value3');
url.searchParams.delete('key1');
console.log(url.toString());

Then we get back https://example.com/?key2=value3. Note that it removes all search parameters with the given key.

Conclusion

JavaScript, like any other programming language, has many handy tricks that let us write our programs more easily. In this article, we looked at how to manipulate URLs with the URL object with various properties and manipulate query strings with the URLSearchParams object. With these tricks, we reduce the effort we put into writing our code making our lives easier.