Categories
Object-Oriented JavaScript

Object-Oriented JavaScript — Promises

JavaScript is partly an object-oriented language.

To learn JavaScript, we got to learn the object-oriented parts of JavaScript.

In this article, we’ll look at JavaScript promises.

Promises

A promise is an alternative to callbacks.

Promises let us retrieve the results of an async function calls.

Promises are easier than callbacks and gives us more readable code.

They can take on a few states.

A promise can be pending, which means the result isn’t ready yet.

This is the initial state.

A promise is fulfilled when the result is ready.

If there’s an error, then the result is rejected.

When a pending promise is fulfilled or rejected, associated callbacks that are queued up by then are run.

This is better than async callbacks.

If we have many async functions we want to run, then we’ll have to nest them repeatedly.

For instance, we write:

asyncFunction(arg, result => {
  asyncFunction(arg, result => {
    //...
  })
})

to run an async function more than once.

If we have to do that more, then there’s more nesting and it’ll be harder to read.

If asyncFunction is a promise, then we can just call then with a callback.

And the callback run with the promise is fulfilled:

asyncFunction(arg)
  .then(result => {
    //...
  });

We just keep calling then until we called all the promises we want:

asyncFunction(arg)
  .then(result => {
    //...
    return asyncFunction(argB);
  })
  .then(result => {
    //...
  })

The then callback returns a promise, so we can keep calling then until we’re done.

To catch promise errors, we can call the catch method with a callback.

For instance, we can write:

readFileWithPromises('text.json')
  .then(text => {
    //...
  })
  .catch(error => {
    console.error(error)
  })

Creating Promises

We can create promises with the Promise constructor.

For instance, we can write:

const p = new Promise(
  (resolve, reject) => {
    if (...) {
      resolve(value);
    } else {
      reject(reason);
    }
  });

We passed in a callback that has the resolve and reject parameters, which are all functions.

resolve fulfilled the promise with a given value.

reject rejects a promise with a value.

We can use than with then and catch as we did with the previous examples:

p
  .then(result => {
    //...
  })
  .catch(error => {
    //...
  })

When we throw an error in the then callback, it’ll also be caught with catch if it’s called after the then that throws the error:

readFilePromise('file.txt')
  .then(() => {
    throw new Error()
  })
  .catch(error => {
    'Something went wrong'
  });

Promise.all()

Promise.all lets us run an array of promises in parallel and returns a promise that resolves to an array of all the promises in the array.

If all of them are fulfilled, then an array of the results of the promises will be returned in then then callback’s parameter.

For instance, we can write:

Promise.all([
    p1(),
    p2()
  ])
  .then(([r1, r2]) => {
    //
  })
  .catch(err => {
    console.log(err)
    ''
  })

We call Promise with p1 and p2 which return promises.

Then r1 and r2 are the results.

catch catches error from any of the promises if any of them are rejected.

Conclusion

We can create promises with the Promise constructor.

Promise.all run multipole promises in parallel and returns a promise that resolves to an array of all the results,.

Categories
Object-Oriented JavaScript

Object-Oriented JavaScript — Parts of a Class

JavaScript is partly an object-oriented language.

To learn JavaScript, we got to learn the object-oriented parts of JavaScript.

In this article, we’ll look at the parts of a JavaScript class.

Constructor

The constructor is a special method that’s used to create and initialize the object we create with the class.

The class constructor can call its parent class constructor with the super method.

super is used for creating subclasses.

Prototype Methods

Prototype methods are prototype properties of a class.

They’re inherited by instances of a class.

For instance, we can write:

class Car {
  constructor(model, year) {
    this.model = model;
    this.year = year;
  }

  get modelName() {
    return this.model
  }

  calcValue() {
    return "2000"
  }
}

We have 2 prototype methods within our Car class.

One is the modelName getter method.

And the other is the calValue method.

We can use them once we instantiate the class:

const corolla = new Car('Corolla', '2020');
console.log(corolla.modelName);
console.log(corolla.calcValue());

We created the Car instance.

Then we get the getter as a property.

And we called calcValue to get the value.

We can create class methods with dynamic names.

For instance, we can write:

const methodName = 'start';

class Car {
  constructor(model, year) {
    this.model = model;
    this.year = year;
  }

  get modelName() {
    return this.model;
  }

  calcValue() {
    return "2000"
  }

  [methodName]() {
    //...
  }
}

We pass in the methodName variable to the square brackets to make start the name of the method.

Static Methods

We can add static methods that can be run directly from the class.

For instance, we can write:

class Plane {
  static fly(level) {
    console.log(level)
  }
}

We have the fly static method.

To run static methods, we can write:

Plane.fly(10000)

Static Properties

There’s no way to define static properties within the class body.

This may be added in future versions of JavaScript.

Generator Methods

We can add generator methods into our class.

For instance, we can make a class where its instances are iterable objects.

We can write:

class Iterable {
  constructor(...args) {
    this.args = args;
  }

  *[Symbol.iterator]() {
    for (const arg of this.args) {
      yield arg;
    }
  }
}

to create the Iterable class that takes a variable number of arguments.

Then we have the Symbol.iterator method to iterate through this.args and return the arguments.

The * indicates that the method is a generator method.

So we can use the class by writing:

const iterable = new Iterable(1, 2, 3, 4, 5);
for (const i of iterable) {
  console.log(i);
}

then we get:

1
2
3
4
5

We have created an instance of the Iterable class.

Then we looped through the iterator items by and logged the values.

Conclusion

A class can have a constructor, instance variables, getters, instance methods, static methods, and generator methods.

Categories
Object-Oriented JavaScript

Object-Oriented JavaScript — Multiple Inheritance

JavaScript is partly an object-oriented language.

To learn JavaScript, we got to learn the object-oriented parts of JavaScript.

In this article, we’ll look at multiple inheritance.

Multiple Inheritance

We can do multiple inheritance easily by merging properties from different properties into one object and then return the object.

For instance, we can write:

function multi(...args) {
  let obj = {};
  for (const arg of args) {
    obj = {
      ...obj,
      ...arg
    };
  }
  return obj;
}

Then we can use it by writing:

const obj = multi({
  foo: 1
}, {
  bar: 2
}, {
  baz: 3
});

obj is then:

{foo: 1, bar: 2, baz: 3}

We pass in multiple objects and then copied the properties with the spread operator.

Mixins

Mixins is an object that can be incorporated into another object.

When we create an object, we can choose which mixin to incorporate into the final object.

Parasitic Inheritance

Parasitic inheritance is where we take all the functionality from another object into a new one.

This pattern is created by Douglas Crockford.

For instance, we can write:

function object(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

const baseObj = {
  name: '2D shape',
  dimensions: 2
};

function rectangle(baseObj, width, height) {
  const obj = object(baseObj);
  obj.name = 'rectangle';
  obj.getArea = function() {
    return this.width * this.height;
  };
  obj.width = width;
  obj.height = height;
  return obj;
}

const rect = rectangle(baseObj, 2, 3);
console.log(rect);
console.log(rect.getArea());

We have the object function which returns an instance of F .

Then we created the rectangle function that incorporates the baseOf , width and height and incorporate that into the object returned by object .

object takes the baseObj into the prototype of F .

And we add own properties to obj , which we return to add more properties.

This way if we log the value of rect , we get:

{name: "rectangle", width: 2, height: 3, getArea: ƒ

And __proto__ for rect has:

dimensions: 2
name: "2D shape"

We can also call the getArea as we did in the last console log, and we get 6.

So we know this is referring to the returned object.

Borrow a Constructor

We can call a parent constructor from a child constructor.

For instance, we can write:

function Shape(id) {
  this.id = id;
}

Shape.prototype.name = 'Shape';
Shape.prototype.toString = function() {
  return this.name;
};

function Square(id, name, length) {
  Shape.apply(this, [id]);
  this.name = name;
  this.length = length;
}

Square.prototype = new Shape();
Square.prototype.name = 'Square';

We have the Shape constructor, which takes the id parameter.

We call the Shape constructor with apply so that we this is set to the Shape constructor.

This will set this.id from Square .

Then we can populate our own properties.

And then we create Square ‘s prototype with the Shape instance.

Then we set the name to 'Square' since this is shared because of all the Square instances.

The way, we copy the properties from Shape to Square by setting Square’s prototype property.

Conclusion

We can create objects with various ways of inheritance.

We can call the parent’s constructor and incorporate various properties from various objects.

Categories
Object-Oriented JavaScript

Object-Oriented JavaScript — Modules

JavaScript is partly an object-oriented language.

To learn JavaScript, we got to learn the object-oriented parts of JavaScript.

In this article, we’ll look at JavaScript modules.

Modules

JavaScript modules let us divide our code into multiple small chunks.

Modules in JavaScript are just regular JavaScript files.

There’s no module keyword.

We just use the export keyword to make items in modules available to other modules.

For instance, we can write:

module.js

export const port = 8080;
export function startServer() {
  //...
}
export class Config {
  //...
}
export function process() {
  //...
}

to create a module with various members exported.

We can export variables, functions, classes, etc.

Then we can import it by writing:

import * from './module'

We import all members from a module with the asterisk.

Also, we can import individual members by writing:

import { Config, process } from "./module";

We import the proces function and the Config class from module .

If we only have only one thing to export, we can use the default export syntax.

For instance, we can write:

export default class {
  //...
}

then we export the class in module.js .

Then we import it by writing:

import C from "./module";

Modules have some special properties.

They’re singletons, which means that it’s imported only once in another module.

Variables, fucmtions and other declarations are local to the module.

Only the ones that are marked with export are available for import .

Modules can import other modules.

We did that with the import statement we used earlier.

The path is relative in the examples.

But we can import modules with the module name if they’re in the node_modules folder.

ES5 supports libraries with CommonJS and Asynchronous Module Definition (AMD) module systems.

These aren’t part of the language spec.

They’re 3rd party systems that are adopted for use since ES5 and earlier don’t have a native module system.

CommonJS is the default module system that Node.js uses.

Export Lists

We can export a bunch of items in one line.

For instance, we can write:

const port = 8080;

function startServer() {
  //...
}
class Config {
  //...
}
function process() {
  //...
}

export { port, startServer, Config, process };

Then we export all the variables, classes, and functions that are above it.

We can then import them with import in another module.

The name that we import the members with can be changed with the as keyword.

For instance, we can write:

import { process as processConfig } from "./module";

We import the process function to processConfig with the as keyword.

Also, we can rename exports.

For instance, we can write:

module.js

const port = 8080;
function startServer() {
  //...
}
class Config {
  //...
}
function process() {
  //...
}

export { port, startServer, Config, process as processConfig };

Then we can import it bu writing:

import { processConfig } from "./module";

We used as on an export to rename it.

Conclusion

We can use modules to divide our code into small chunks.

This way, we won’t have to use global variables or have large pieces of code.

Categories
Object-Oriented JavaScript

Object-Oriented JavaScript — Maps, Sets, WeakSets, and WeakMaps

JavaScript is partly an object-oriented language.

To learn JavaScript, we got to learn the object-oriented parts of JavaScript.

In this article, we’ll look at maps iteration, sets, WeakSets, and WeakMaps.

Iterating Over Maps

We can iterate over maps with the keys method to iterate over the keys.

For instance, we can write:

const m = new Map([
  ['one', 1],
  ['two', 2],
  ['three', 3],
]);

for (const k of m.keys()) {
  console.log(k);
}

Then we get:

one
two
three

from the console log.

We can iterate over the values with the values method:

for (const v of m.values()) {
  console.log(v);
}

Then we get:

1
2
3

from the console log.

We can loop through the entries with the entries method:

for (const [k, v] of m.entries()) {
  console.log(k, v);
}

Then we get:

one 1
two 2
three 3

We restructured the key and value returned from the entries method.

Converting Maps to Arrays

We can convert maps to arrays with the spread operator.

For instance, we can write:

const m = new Map([
  ['one', 1],
  ['two', 2],
  ['three', 3],
]);

const keys = [...m.keys()]

to get the keys from the map and convert it to an array.

So we get:

["one", "two", "three"]

We can also spread the map to spread the key-value pairs into a nested array of key-value pairs.

So if we have:

const m = new Map([
  ['one', 1],
  ['two', 2],
  ['three', 3],
]);

const arr = [...m]

Then we get:

[
  [
    "one",
    1
  ],
  [
    "two",
    2
  ],
  [
    "three",
    3
  ]
]

as the value of arr .

Set

A Set is a collection of values that can’t have duplicate entries.

For instance, we can write:

const s = new Set();
s.add('first');

Then we create a set with an entry.

add adds an entry.

has checks if a value exists. We can use it by writing:

s.has('first');

delete lets us delete a value:

s.delete('first');

We can also create an array, so we can write:

const colors = new Set(['red', 'green', 'blue']);

to create a set with the strings.

WeakMap and WeakSet

WeakMap and WeakSet are similar to Map and Set but are more restricted.

WeakMaps only have the has(), get(), set(), and delete() methods.

WeakSet s only have the has() , add() , and delete() methods.

Keys of WeakMap must be objects.

We can only access a WeakMap value with its key.

Values of WeakSet must be objects.

We can’t iterate over a WeakMap or WeakSet .

We can’t clear a WeakMap or WeakSet .

WeakMap s are useful when we don’t want any control over the lifecycle of the object we’re keeping with it.

It’ll automatically be garbage collected when the key is no longer available.

Conclusion

Maps, Sets, WeakMaps, and WeakSets are useful data structures that we can use with our app.