Wednesday, December 18, 2019

Super-test with mocking middleware In Jest

So, today we are going to see how do we mock the middleware if it is applied over the Express server.

So, here is our agenda:
  1. Create an express app
  2. Add a middleware to Authenticate the user
  3. Test the app using Jest (integration test NOT UNIT)
index.js

1
2
3
const server = require('./server')
const PORT = process.env.PORT || 3000
server.listen(PORT, () => console.log(`Server is live at localhost:${PORT}`))

server.js
1
2
3
4
5
6
7
const express = require('express')
const middleware = require('./middleware')
const welcomeRoute = require('./welcome-route')
const server = express()
server.use(express.json())
server.use('/api', middleware, welcomeRoute)
module.exports = server


is-user-authentic.js
1
2
3
4
5
const faker = require('faker/locale/en_US')
const isUserAuthentic = () => faker.random.arrayElement([true, false])
module.exports = {
  isUserAuthentic
}

middleware.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const { isUserAuthentic } = require('./is-user-authentic')

const middleware = (req, res, next) => { 
  if (!isUserAuthentic()) {
    res.sendStatus(401)
    return next(new Error('Unauthenticated user'))
  }
  next()
}
const myExport = module.exports = middleware

welcome-route.js
1
2
3
4
5
const { Router } = require('express')
const WelcomeService = require('./welcome-service')
const router = Router()
router.get('/', (_req, res) => res.json({ 'message': WelcomeService.message() }))
module.exports = router

welcome-service.js
1
2
3
module.exports = {
  message: () => 'hello world'
}

In this code, the flow is like:
  1. Start the server at 3000 (line 3, index.js)
  2. Middleware is applied to base path (line 6, server.js)
  3. If you hit http://localhost:3000/api, is-user-authentic will return true/false randomly which will return either return 'hello world' or 401 unauthorised
Now in order to test this, we will use jest mocking.

routes.test.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
const request = require('supertest')
const faker = require('faker/locale/en_US')

let app
let WelcomeServiceSpyOfMessage
let IsUserAuthenticSpyOnIsUserAuthentic

describe('test', () => {
  beforeEach(() => {

    /**
     * Always create spy then create server
     */
    const WelcomeService = require('../src/welcome-service')
    WelcomeServiceSpyOfMessage = jest.spyOn(
      WelcomeService,
      'message',
    )
    
    /**
     * Always create spy then create server
     */
    const IsUserAuthentic = require('../src/is-user-authentic')
    IsUserAuthenticSpyOnIsUserAuthentic = jest.spyOn(
      IsUserAuthentic,
      'isUserAuthentic'
    )
    app = require('../src/server')
  })

  afterEach(() => {
    /**
     * Most important since b'coz of caching, the mocked implementations sometimes does not resets 
     */
    jest.resetModules()
    jest.restoreAllMocks()
  })

  it('1. Mock implementation, with successful user auth [isUserAuthentic as true]', async () => {
    const mockedMessage = faker.lorem.sentence()
    WelcomeServiceSpyOfMessage.mockImplementation(() => mockedMessage)
    // --> For successful user authentication, sending true :
    IsUserAuthenticSpyOnIsUserAuthentic.mockImplementation(() => true)
    const result = await request(app)
      .get('/api')
    expect(result.statusCode).toBe(200)
    expect(result.body).toHaveProperty('message', mockedMessage)
  })

  it('2. After restored implementation, with successful user auth [isUserAuthentic as true]', async () => {
    /**
     * This will return { 'message' : 'hello world'},
     * since we restored the mock implementation, in afterEach
     * restoreAllMocks and resetModules,
     * works only for methods those are mocked
     * with .spyOn()
     */
    // --> For successful user authentication, sending back true
    IsUserAuthenticSpyOnIsUserAuthentic.mockImplementation(() => true)
    const result = await request(app)
      .get('/api')
    expect(result.statusCode).toBe(200)
    /**
     * This expectation is the same returning from the service
     */
    expect(result.body).toHaveProperty('message', 'hello world')
  })

  it('3. Returning user is Unauthorized, [isUserAuthentic as false]', async () => {
    // for Unauthorized, we return false
    IsUserAuthenticSpyOnIsUserAuthentic.mockImplementation(() => false)
    const result = await request(app)
      .get('/api')
    expect(result.statusCode).toBe(401)
  })
})

In this test there are certain things that needs to be taken care before loading the express server.

  1. Mock the middleware before you load the server otherwise if you mock middleware after the server is loaded new reference of the object will be mocked and even after the mock the original implementation will be called.
  2. In this test case, we loaded the server at line 28, but the spy was already hooked to the functions before that only.
  3. In afterEach we will reset modules and reset mocks to make sure mocked implementation of one test will not interfere in other tests.
  4. Now every time module is loaded then spy is added and mock will be hooked.
The source code for the above example is available at https://github.com/ankur20us/demo-jest-supertest

Happy Coding
:) 

Saturday, July 27, 2019

Sherlock and the Valid String - HackerRank - Easiest Solution

Hello everyone, today, while going through the problem of Sherlock & Valid String, I literally tried a lot and was not getting a simple and sober solution.

In fact, when I myself started implementing it, I found that the problem is not that complicated the way the solutions are available in other blogs, so I started the problem and here is the solution I came up with.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function isValid(s) {
  const countMap = {}; const countList = [];
  const arr = s.split('');
  arr.forEach((a) => countMap[a] = (countMap[a] || 0) + 1);
  Object.keys(countMap).forEach(k => countList.push(countMap[k]));
  countList.sort();
  const countSet = new Set(countList);

  const len = countList.length;

  if (countSet.size > 2) {
    return 'NO';
  } else if (countSet.size === 1) {
    return 'YES';
  } else if (countSet.size === 2) {
    const firstNum = countList[0];         const lastNum = countList[len - 1];
    const lastList = countList.slice(1);   const firstList = countList.slice(0, len - 1);
    const lastListSet = new Set(lastList); const firstListSet = new Set(firstList);
    return (
      (lastListSet.size === 1 && firstNum === 1)
      || (lastListSet.size === 1 && lastNum - firstNum === 1)
      || (firstListSet.size === 1 && lastNum - firstNum === 1)
    )
      ? 'YES' : 'NO';
  }
}

Explanation

Suppose the input was aabbc:

  1. Create a countMap with the number of occurrences of each character.
    • countMap = { a: 2, b: 2, c:1 };
  2. Make a list of the values of above maps( not keys ) and sort it.
    • countList = [1, 2, 2]
  3. Convert countList to countSet
    1. countSet = [1, 2]
  4. If (countSet.size > 2) the solution is not possible.
  5. If (countSet.size === 1), we have the "YES"
  6. If (countSet.size === 2), we have to process:
    1. We will create 6 variables for our ease, those are:
      1. firstNum  i.e first number of countList (sorted) = 1 (for our example)
      2. lastList i.e sub array of countList from 1..end = [2, 2]
      3. lastListSet i.e set over lastList, for removing duplicates = [2]
      4. lastNum i.e last number of countList (sorted) = 2
      5. firstList i.e sub array of countList from 0..end-1 = [1, 2]
      6. firstListSet i.e set over firstList, for removing duplicates = [1, 2]
    2. There are only 3 conditions which is "YES" those are:
      1. [ 1, X, X, X, X]
      2. [ X-1, X-1, X-1, X-1, X]
      3. [X, X, X, X, X+1]
    3. For our example 2.1 is true
    4. Apart from them there is no other way, a String will give "YES" and thats what we did

Happy coding,
:)




Saturday, May 18, 2019

Node & its roots. 😃

(WIP) So, Node Js is one of the best thing that has ever happened to Technological world. There is no doubt in that.

Now coming to the language, NodeJs is the JAVASCRIPT language with the ability to run in server side ( unlike in FRONT end / browser).

So, how it does what it does, I mean, how Node JS actually gave us the ability to run the stuff in an Event loop and gives us the capability to code for callbacks, when languages like Java, Ruby are so popular in Synchronous world.

Let us first take a step back and open the Box named NodeJs and see what it has in all:
  1. v8 (JS Engine)
    • It is a JS Runtime that is needed in order to do Parsing, Compiling and Running JS Code, it is written in C/C++. In some of the cases they are also termed as VM's of JS engines.
  2. libuv
    • It is an Async IO library that is based on Event Loops, it is written in C. 
And that's it. Yeah, believe or not these are the 2 pillars of Node Js and its magic behind the scene. Well once you read this blog, you will understand why NodeJs is single threaded and why it is always said don't do heavy operations in Node JS.

Now let's see what is V8? 
  • It is a JS Runtime that is capable of running the code written in JS format but its own code is written in C++.
  • It is C++ library, that can be added as a normal library in any C++ project and you can use that to compile and run JavaScript code.
  • It is the same engine that is used in Chrome Browser behind the seen to execute the JS. 
  • It is JIT ( Just In Time Compiler) that means Compile which means 
    1. Compile Little Bit 
    2. Run 
    3. Collect data which can be used for next run
    4. Repeat 1 
  • Implemented for JS that follows EcmaScript Specification
  • In case EcmaScript updated V8 is updated
  • It is a Garbage Collected Language that means unused objects are Garbage Collected and this operation is pretty heavy in execution and automatic in nature.
  • It is not having DOM, Console or File System Access where DOM and Console is the responsibility of the Browser, File System Access is given by Libuv.
How the code is moved from Parsing to Execution?

In the first phase,
  1. the JS code is basically taken in and parsed to create AST (Abstract Syntax Tree) 
  2. AST then interpreted and converted to byte code to get better speed since it is an optimised version of our code for better understanding by the machine 
  3. and then finally code is executed. In case if a function or part of code is being used frequently, then that function is passed to Turbo Fan component, which compiles that function with the information that is calculated in between the compilations of JIT architecture, to run more faster, so basically it acts as a cache for common used functions.
In the context of Javascript, we know that browser mainly executes JS without the support of low level API's like File system. 


So the question that comes to mind is that, do we really need Browser to execute Javascript?
Well the answer is straight NO

So if Browser is not needed to run the JS code then who is responsible to run the JS code, so the answer is the VM (Virtual Machines) of the JS engines (V8, in case of default distribution of Node, Chakra is another that can replace V8) 


Node Js does most of the task by using LIBUV and by using V8, it gives a non blocking interface (async) that can be called from JS Code. 

Now the question that comes in mind is that 

Tuesday, April 23, 2019

Flow with the JS types.

Things are getting easier day by day in order to code in JS world and in order to fill the gap of datatypes between the function definition and function call, for the params, Facebook introduced the API called Flow, https://github.com/facebook/flow

The FB Flow is basically a way to define the datatype of the variable and it cross-checks that while passing the values to the Datatypes the values are passed in correct datatype format or not.

In a simple language, now you can say that a variable is going to be numeric in nature and you are not allowed to pass a string to that variable, if you do so while compiling the project itself, you will get an error from the Flow system regarding the same.

Dynamic binding is not going to be possible so openly as it used to be before.

So let's take a basic example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* @flow */
'use strict';

// `x` is declared to be a `number` explicitly.
// This means Flow will throw an error if we try to assign
// any value to `x` which is not a `number`.
var x: number = 5;

// this will not generate the error, since you are pushing number to number
x = 1234;

// #FlowExpectError – string does not match type of x, which is number
x = 'five';

// If we don't declare variable type explicitly, it's not sealed.
var y = true;

y = false;

// We are free to change the type any time by assigning a new value to the variable
y = 'hello';

// #FlowExpectError – Flow still type checks `y`, it knows which type it is currently.
var z: boolean = y;

In this code, we have used the Datatype binding at line number 7, so in that line, we have created a variable named "x" and told flow to make the datatype as Number.

Flow, in a nutshell, is like a preprocessor that is going to check the code against the datatypes and going to tell the exception on the basis of that.

After that, we have to compile the code which means we have to remove the code which we have added and which is not the part of the Javascript world like line number 7.

In order to check the code, we have an executable that is provided by flow module.  The executable reads a file called .flowconfig which contains the settings for Flow module.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[ignore]
.*/node_modules/*

[include]

[libs]

[lints]

[options]
suppress_comment=\\(.\\|\n\\)*\\#FlowExpectError
suppress_comment=\\(.\\|\n\\)*\\#FlowIgnoreAsset
esproposal.class_instance_fields=enable
esproposal.class_static_fields=enable

[strict]

The linting provided by .flowconfig, with properties that can be found here:  https://flow.org/en/docs/config/

Also, Flow executable takes the parameters in the command line as well and even that info is available at the above link.

In order to check the validity of the code we have written, we can use the flow executable against the folder src/ (which is by default picked by flow).

The command for checking and building is added in package.json


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "name": "demo-facebook-flow",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "check": "npx flow",
    "build": "npx flow-remove-types src/ -d lib/"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "flow-bin": "^0.93.0",
    "flow-remove-types": "^1.2.3"
  }
}

Line number 8 and 9 is respectively the commands for checking and building (which means remove the code that a pure javascript engine don't understand )

After building it will create a folder named lib that will contain the code of our files without the flow types and that is executable as a normal node file.

Happy coding :)

Monday, April 15, 2019

AjV the schema of the JSON

So, as the internet is progressing day by day, the data transfer is relying more on JSON nowadays, because of its easiness and acceptability in Javascript by default.

Now in order to transfer JSON between the Client and Server, the server mainly needs the input from the Client in JSON and that too in a specific format, like:

1
2
3
{
    "email" : "<EMAIL>"
}

In this example, the server is expecting the string that is being passed in the email key should be the string with email format.

So in order to do that, we can take help of JSON Schema Validation, wherein which we create a SCHEMA for our objects and then validate our objects against it, in order to check whether or not the object is following the schema or not.

The specification of the JSON schema is available here: https://json-schema.org/specification.html

Again there are many validators that are built in the lines of the specification in order to check the validity. Here are some of them:
Out of almost all the validators, AJV is the fastest, here are the results of the comparisons, https://github.com/epoberezkin/ajv

Now let's make our hands dirty into the coding pool 😆

Here is the plan:
  1. We are going to define the Schema and expose via JSON object
  2. Create an object of Ajv
  3. Take Schema and Object we want to test and validate using AjV object

Also, some of the things are not available by JSON schema Specification can be provided by Plugins of the validators, one such plugin is error-messages-ajv - https://github.com/epoberezkin/ajv-errors

So for this example, we are going to use the features provided by error messages plugin. 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
let schema = {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
        },
        "type": {
            "type": "string",
            "maxLength": 10,
            "minLength": 2,
            "errorMessage": {
                "type": "should be a string",
                "maxLength": "can't exceed max length of 10",
                "minLength": "should be greater than length of 2"
            }
        },
        "rank": {
            "type": "integer",
            "maximum": 1000,
            "minimum": 1
        },
        "isDemocratic": {
            "type": "boolean"
        }
    },
    "required": [
        "name",
        "type",
        "rank",
        "isDemocratic"
    ]
}

module.exports = schema;

The schema is pretty self-explanatory and also, at line 13, 14, 15 we have defined what message to return in case of, which of the condition is not met, so for example, in case, if the length of type is more than 10, then since maxLength is not met, we will get the message of line 14  - can't exceed max length of 10 etc 

This feature of different error messages is not available in pure JSON schema specification. So this plugin comes handy when we want to provide different messages with respect to different failures.

Here is the actual code for validation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
 * we kept the schema id to 'auto' to support draft-04, draft-05, draft-06, draft-07, of json schema validation specification  
 */
const ajv = new require('ajv')({
    schemaId: 'auto',
    allErrors: true,
    jsonPointers: true
});
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
/**
 * We added ajv-errors module to our ajv schema engine since we want to give our custom messages in case
 * of data checking
 * 1. keepErrors decide whether or not we want to keep original errors given by ajv engine
 * 2. singleError tells whether to check full json in one go and prompt for all errors or stop 
 *    validation as soon as first error is encountered
 */
require('ajv-errors')(ajv, {
    keepErrors: false,
    singleError: false
});

const schema = require("./schema");
const compiledSchema = ajv.compile(schema);

const correctData = {
    name: "INDIA",
    type: "COUNTRY",
    rank: 1,
    isDemocratic: true
}
/**
 * This is going to be true, since we followed all the norms of the schema
 */
const validForCorrectData = compiledSchema(correctData);
console.log('I am with correct data ', validForCorrectData);

/**
 * Here we passed a wrong format of 'type' param, it is mentioned to be 'String' but it is 'boolean' here
 * because of that the error message of type is going to be picked up
 */
const inCorrectData = {
    name: "INDIA",
    type: true,
    rank: 1,
    isDemocratic: true
}
const validForInCorrectData = compiledSchema(inCorrectData);
console.log('I am with incorrect data ', validForInCorrectData);
if (!validForInCorrectData) console.log(compiledSchema.errors);

In this code, we have taken into consideration the error message plugin as well.

Behind the scene, AjV (after getting the JSON object of the schema) creates a function and returns the reference of that function to us, at line 23.

And if you see at line 34 & line 47 we are using the function to validate our object against the schema

The code is available here, https://github.com/ankur20us/demo-ajv

Happy coding :) 

Sunday, January 20, 2019

Setting up basic typescript project

Typescript is getting popularity day by day, it is basically a different version of Javascript and has more features as well as more properties of a Typed Language as opposed to the default behavior of the Javascript.

Here is a basic example of a function in typescript:

1
const add = (a: number, b: number) => a + b;

As you can see, the part of the code where we have declared a: number and b: number is not a pure Javascript rather, its a feature provided by Typescript and is very useful in a real sense.

Today we are going to configure a normal typescript project and for that, you need the following prerequisites:
  1. npx module installed globally 
  2. typescript module installed globally
  3. tslint module installed globally
For all the above 3 just type: npm install -g npx typescript tslint and it will install all the 3 modules. 
typescript comes with 2 executables namely: tsc and tsserver

tsc  is the typescript compiler that compiles the .ts files and converts them to pure JS files.
tsserver is basically the typescript server.
  1. First, we will create a config file for the typescript project, for that we will use the command:
    tsc --init (- in the folder where we want to start our typescript project.)
  2. This will generate a tsconfig.json file with the following content:

    1
    2
    3
    4
    5
    6
    7
    {
      "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "strict": true
      }
    }

  3. We are going to add some more options in this configuration, So after that, the tsconfig.json will look like:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    {
      "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "strict": true,
        "outDir": "dist",
        "sourceMap": true,
      },
      "include": [
        "src/**/*"
      ],
      "exclude": [
        "node_modules",
        "**/*.spec.ts"
      ]
    }
    

  4. Now we will create a script to compile the .ts files and convert them to .js files. For that, we will take npm's help.
  5. For that, we will initiate the npm and add the script for the conversion.
  6. npm init -y will generate the default package.json with the basic setup. Just add these 2 sections in package.json:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    .......
      "scripts": {
        "test": "echo \"Error: no test specified\" &amp;&amp; exit 1",
        "compile": "rimraf dist/ & tsc"
      },
      "devDependencies": {
        "rimraf": "^2.6.3",
        "typescript": "^3.2.2"
      },
      .......
      .......
    

  7. Now let us create a typescript file with .ts extension and then we will compile the .ts into .js.
  8. Here is a basic .ts file: src/index.ts

    1
    2
    3
    4
    5
    function helloWorld(word: string = "Ankur"): string {
        return `Hello ${word}`;
    }
    
    console.log(helloWorld("World"));
    

  9. Here is the folder structure:

  10. If you have node_modules folder skip this otherwise run:  npm install 
  11. After all is set up, run the command npm run compile (we are using the part of point 6, at line number 4)
  12. After this command, a folder named dist will get generated and it will look like this:


  13. Now you can run the file using node dist/index.js command.
The main purpose of this project is that we are going to write the code in typescript and then we are going to compile the same in pure javascript so that it can be run using the node js framework since node js does not understand the typescript.

The above code is available here.

We have a shortcut way as well to do all the tiring work by itself, using a generator of the Typescript project namely gts, which can be found https://github.com/google/ts-style.

For the shortcut here are the commands:
  1. npx gts init
  2. It will ask you whether or not to generate the package.json, press (y) and enter.
  3. It will generate the project with the following structure (with node_modules/), (I deleted it intentionally, since showing the structure is not possible with that folder.):

  4. This project will have all the basic settings up and ready with typescript settings that are being used by Google Node Js team.
  5. Write your personal code inside src/ and the scripts are mentioned in package.json that are available like:
    1. npm run fix - Will fix the inconsistent & messy code 
    2. npm run check - Will check the code with the guidelines mentioned in tsconfig.json and outputs the problem
    3. npm run clean - Will cleans the code with respect to linting errors, and all possible fixes.
    4. npm run compile - Will take your .ts code and builds it to .js files under build/
Note:
  • In case you use console.log in your code (in the project created by gts), you might get the error like this:

  • In this case, go to node_modules/gts/tsconfig-google.json and add "dom" in lib key (which is an array of strings in nature), finally, the option will look like this:  "lib": ["es2016", "dom"],
  • Come back to the main file and compile the project, you will not see the error.

That's it for typescript basic demo, meet you all soon, thanks and

Happy Coding :)

Monday, January 14, 2019

Iterators in Javascript

Javascript is becoming mature day by day, and in order to make it much interactive and better in the ways of coding, ECMAScript keeps on adding different features to it.

One of the new features (not so new, 😜), is the Iterators & Iterables.

So in a layman language, Iterators & Iterables go hand in hand.

Iterables are those objects(Classes in the real sense) those have implemented the Iterator function.

Now comes the question what is iterable, so, anything that can be iterated is termed as Iterable and the property that makes it iterable is the Iterator.

1. Explanation

So, here is the quick info regarding the things we read till now:
  1. Iterable: is basically a property that needs to be implemented to make a class/object iterable. This is done by implementing a method whose key is Symbol.iterator. Symbol.iterator is a factory of iterators.
  2. Iterator: is a function that returns a function named next() which again returns an object with 2 keys namely: done & value, so a basic iterator() looks like this:

    1
    2
    3
    4
    5
    6
    7
    8
    return{
        next(){
            return({
                done: true/false, //boolean
                value: item, //item to be read
            })
        }
    }
    
2. Questions

2.1 Q:
Now the question arises, where are these iterators useful, so let's take a look at the traversing an array in a normal fashion.

1
2
3
let a = [1, 2, 3, 4];
for(let i=0; i < a.length; i++)
   console.log(a[i]);

A: Same functionality with for..of loop, that internally uses the iterators, to traverse the array or any iterable.

1
2
for(let v of a)
	console.log(v)

As you can see how clean and compact it is, to traverse an iterable if the iterators are implemented in a proper manner.

2.2 Q: Now how do you tell whether a class implemented the iterator or not?

A: If console.log(typeof array[Symbol.iterator]) returns a function that means the Iterator is implemented and the object of that class can be browsed or traversed using the special for loops.

Now let us take a basic example, where I created the Class with 2 variables and then I implemented the iterator in order to traverse all the variables of the object of the same class.

For the reference here is the code.

Let us try to see the code in detail. So basically code has 2 files, User.js (the class) and index.js (file that created the object of User class and tries to utilize iterator.)

User.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class User {
    constructor({name, age}){
        this.name = name;
        this.age = age;
    }

    getAge(){ return this.age; }
    getName(){ return this.name; }

    setName(name){ this.name = name; }
    setAge(age){ this.age = age; } 

    //this is the property that needs to be implemented for iterators
    [Symbol.iterator]() {
        let self = this;
        let data = Object.keys(this);
        return{
            next(){
                return({
                    done: data.length === 0,
                    value: self[data.pop()]                
                })
            }
        }
    }
}

module.exports = User;

index.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let User = require("./User");

let user1 = new User({name: 'AV', age: 31});
let user2 = new User({name: 'AV', age: 32});

console.log('\nIterating USER1')
for(const a of user1) {
    console.log(a)
}
console.log('\nIterating USER2')
for(const a of user2) {
    console.log(a)
}

The code of User.js is pretty simple, we have created a class that takes 2 parameters namely, name & age, but the catch is at line no 14, User.js.

At line 14, User.js, we have implemented the iterator for the class User, and in order to do that we have called a function named [Symbol.iterator] & have provided the functionality of the iterator. In that iterator, we have returned a function at line 17, with the structure we discussed at point 1.2 (under Explanation section)

The iterator is a function that returns a function named next() & it internally returns an object with 2 keys, namely done (line no 20, User.js) & value (line no 21, User.js).

In the iterator, we are taking the value from the self-object (line no 15, User.js) and the done value is the boolean that checks whether all values of the object are traversed or not.

The code of index.js is pretty straight forward, as we are just traversing the object (keys) using for..of loop at line 7 & 11, index.js.

Note: The example I took is a basic example of showing the functionality of Iterators, although implementing the iterator in this fashion is not a good practice.

Hope you enjoyed and understood the Iterators.


Happy coding.
:)