Categories
Vue

How to Make a Chrome Extension with Vue.js

The most popular web browsers, Chrome and Firefox support extensions. Extensions are small apps that you can add to your browser to get the functionality that is not included in your browser. This makes extending browser functionality very easy. All a user has to do is to add browser add-ons from the online stores like the Chrome Web Store or the Firefox Store to add browser add-ons.

Browser extensions are just normal HTML apps packages in a specific way. This means that we can use HTML, CSS, and JavaScript to build our own extensions.

Chrome and Firefox extensions follow the Web Extension API standard. The full details are at https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions

In this article, we will make a Chrome extension that displays the weather from the OpenWeatherMap API. We will add a search to let users look up the current weather and forecast from the API and display it in the extension’s popup box.

We will use Vue.js to build the browser extension. To begin building it, we start with creating the project with Vue CLI. Run npx @vue/cli create weather-app to create the project. In the wizard, select Babel and Vuex.

The OpenWeatherMap API is available at https://openweathermap.org/api. You can register for an API key here. Once you got an API key, create an .env file in the root folder and add VUE_APP_APIKEY as the key and the API key as the value.

Next, we use the vue-cli-plugin-browser-extension to add the files for writing and compiling the Chrome extension. The package settings and details are located at https://www.npmjs.com/package/vue-cli-plugin-browser-extension.

To add it to our project, we run vue add browser-extension to add the files needed to build the extension. The command will change the file structure of our project.

After that command is run, we have to remove some redundant files. We should remove App.vue and main.js from the src folder and leave the files with the same name in the popup folder alone. Then we run npm run serve to build the files as we modify the code.

Next, we have to install the Extension Reload to reload the extension as we are changing the files. Install it from https://chrome.google.com/webstore/detail/extensions-reloader/fimgfedafeadlieiabdeeaodndnlbhid to get hot reloading of our extension in Chrome.

Then we go to the chrome://extensions/ URL in Chrome and toggle on Developer Mode. We should see the Load unpacked button on the top left corner. Click that, and then select the dist folder in our project to load our extension into Chrome.

Next, we have to install some libraries that we will use. We need Axios for making HTTP requests, BootstrapVue for styling, and Vee-Validate for form validation. To install them, we run npm i axios bootstrap-vue vee-validate to install them.

With all the packages installed we can start writing our code. Create CurrentWeather.vue in the components folder and add:

<template>  
  <div>  
    <br />  
    <b-list-group v-if="weather.main">  
      <b-list-group-item>Current Temparature: {{weather.main.temp - 273.15}} C</b-list-group-item>  
      <b-list-group-item>High: {{weather.main.temp_max - 273.15}} C</b-list-group-item>  
      <b-list-group-item>Low: {{weather.main.temp_min - 273.15}} C</b-list-group-item>  
      <b-list-group-item>Pressure: {{weather.main.pressure }}mb</b-list-group-item>  
      <b-list-group-item>Humidity: {{weather.main.humidity }}%</b-list-group-item>  
    </b-list-group>  
  </div>  
</template>

<script>  
import { requestsMixin } from "@/mixins/requestsMixin";

export default {  
  name: "CurrentWeather",  
  mounted() {},  
  mixins: [requestsMixin],  
  computed: {  
    keyword() {  
      return this.$store.state.keyword;  
    }  
  },  
  data() {  
    return {  
      weather: {}  
    };  
  },  
  watch: {  
    async keyword(val) {  
      const response = await this.searchWeather(val);  
      this.weather = response.data;  
    }  
  }  
};  
</script>

<style scoped>  
p {  
  font-size: 20px;  
}  
</style>

This component displays the current weather from the OpenWeatherMap API as the keyword from the Vuex store is updated. We will create the Vuex store later. The this.searchWeather function is from the requestsMixin , which is a Vue mixin that we will create. The computed block gets the keyword from the store via this.$store.state.keyword and return the latest value.

Next, create Forecast.vue in the same folder and add:

<template>  
  <div>  
    <br />  
    <b-list-group v-for="(l, i) of forecast.list" :key="i">  
      <b-list-group-item>  
        <b>Date: {{l.dt_txt}}</b>  
      </b-list-group-item>  
      <b-list-group-item>Temperature: {{l.main.temp - 273.15}} C</b-list-group-item>  
      <b-list-group-item>High: {{l.main.temp_max - 273.15}} C</b-list-group-item>  
      <b-list-group-item>Low: {{l.main.temp_min }}mb</b-list-group-item>  
      <b-list-group-item>Pressure: {{l.main.pressure }}mb</b-list-group-item>  
    </b-list-group>  
  </div>  
</template>

<script>  
import { requestsMixin } from "@/mixins/requestsMixin";

export default {  
  name: "Forecast",  
  mixins: [requestsMixin],  
  computed: {  
    keyword() {  
      return this.$store.state.keyword;  
    }  
  },  
  data() {  
    return {  
      forecast: []  
    };  
  },  
  watch: {  
    async keyword(val) {  
      const response = await this.searchForecast(val);  
      this.forecast = response.data;  
    }  
  }  
};  
</script>

<style scoped>  
p {  
  font-size: 20px;  
}  
</style>

It’s very similar to CurrentWeather.vue. The only difference is that we are getting the current weather instead of the weather forecast.

Next, we create a mixins folder in the src folder and add:

const APIURL = "http://api.openweathermap.org";  
const axios = require("axios");

export const requestsMixin = {  
  methods: {  
    searchWeather(loc) {  
      return axios.get(  
        `${APIURL}/data/2.5/weather?q=${loc}&appid=${process.env.VUE_APP_APIKEY}`  
      );  
    }, 

    searchForecast(loc) {  
      return axios.get(  
        `${APIURL}/data/2.5/forecast?q=${loc}&appid=${process.env.VUE_APP_APIKEY}`  
      );  
    }  
  }  
};

These functions are for getting the current weather and the forecast respectively from the OpenWeatherMap API. process.env.VUE_APP_APIKEY is obtained from our .env file that we created earlier.

Next in App.vue inside the popup folder, we replace the existing code with:

<template>  
  <div>  
    <b-navbar toggleable="lg" type="dark" variant="info">  
      <b-navbar-brand href="#">Weather App</b-navbar-brand>  
    </b-navbar>  
    <div class="page">  
      <ValidationObserver ref="observer" v-slot="{ invalid }">  
        <b-form @submit.prevent="onSubmit" novalidate>  
          <b-form-group label="Keyword" label-for="keyword">  
            <ValidationProvider name="keyword" rules="required" v-slot="{ errors }">  
              <b-form-input  
                :state="errors.length == 0"  
                v-model="form.keyword"  
                type="text"  
                required  
                placeholder="Keyword"  
                name="keyword"  
              ></b-form-input>  
              <b-form-invalid-feedback :state="errors.length == 0">Keyword is required</b-form-invalid-feedback>  
            </ValidationProvider>  
          </b-form-group> 
          <b-button type="submit" variant="primary">Search</b-button>  
        </b-form>  
      </ValidationObserver><br /> 

      <b-tabs>  
        <b-tab title="Current Weather">  
          <CurrentWeather />  
        </b-tab>  
        <b-tab title="Forecast">  
          <Forecast />  
        </b-tab>  
      </b-tabs>  
    </div>  
  </div>  
</template>

<script>  
import CurrentWeather from "@/components/CurrentWeather.vue";  
import Forecast from "@/components/Forecast.vue";

export default {  
  name: "App",  
  components: { CurrentWeather, Forecast },  
  data() {  
    return {  
      form: {}  
    };  
  },  
  methods: {  
    async onSubmit() {  
      const isValid = await this.$refs.observer.validate();  
      if (!isValid) {  
        return;  
      }  
      localStorage.setItem("keyword", this.form.keyword);  
      this.$store.commit("setKeyword", this.form.keyword);  
    }  
  },  
  beforeMount() {  
    this.form = { keyword: localStorage.getItem("keyword") || "" };  
  },  
  mounted(){  
    this.$store.commit("setKeyword", this.form.keyword);  
  }  
};  
</script>

<style>  
html {  
  min-width: 500px;  
}

.page {  
  padding: 20px;  
}  
</style>

We add the BootstrapVue b-navbar here to add a top bar to show the extension’s name. Below that, we added the form for searching the weather info. Form validation is done by wrapping the form in the ValidationObserver component and wrapping the input in the ValidationProvider component. We provide the rule for validation in the rules prop of ValidationProvider . The rules will be added in main.js later.

The error messages are displayed in the b-form-invalid-feedback component. We get the errors from the scoped slot in ValidationProvider . It’s where we get the errors object from.

When the user submits the number, the onSubmit function is called. This is where the ValidationObserver becomes useful as it provides us with the this.$refs.observer.validate() function for form validation.

If isValid resolves to true , then we set the keyword in local storage, and also in the Vuex store by running this.$store.commit(“setKeyword”, this.form.keyword); .

In the beforeMount hook, we set the keyword so that it will be populated when the extension first loads if a keyword was set in local storage. In the mounted hook, we set the keyword in the Vuex store so that the tabs will get the keyword to trigger the search for the weather data.

Then in store.js , we replace the existing code with:

import Vue from "vue";  
import Vuex from "vuex";Vue.use(Vuex);export default new Vuex.Store({  
  state: {  
    keyword: ""  
  },  
  mutations: {  
    setKeyword(state, payload) {  
      state.keyword = payload;  
    }  
  },  
  actions: {}  
});

to add the Vuex store that we referenced in the components. We have the keyword state for storing the search keyword in the store, and the setKeyword mutation function so that we can set the keyword in our components.

Next in popup/main.js , we replace the existing code with:

import Vue from 'vue'  
import App from './App.vue'  
import store from "../store";  
import "bootstrap/dist/css/bootstrap.css";  
import "bootstrap-vue/dist/bootstrap-vue.css";  
import BootstrapVue from "bootstrap-vue";  
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";  
import { required } from "vee-validate/dist/rules";/\* eslint-disable no-new \*/

extend("required", required);  
Vue.component("ValidationProvider", ValidationProvider);  
Vue.component("ValidationObserver", ValidationObserver);  
Vue.use(BootstrapVue);Vue.config.productionTip = false;new Vue({store,  
  render: h => h(App)  
}).$mount("#app");

We added the validation rules that we used in the previous files here, as well as include all the libraries we use in the app. We registered ValidationProvider and ValidationObserver by calling Vue.component so that we can use them in our components. The validation rules provided by Vee-Validate are included in the app so that they can be used by the templates by calling extend from Vee-Validate. We called Vue.use(BootstrapVue) to use BootstrapVue in our app.

Finally in index.html , we replace the existing code with:

<!DOCTYPE html>  
<html lang="en">  
  <head>  
    <meta charset="utf-8" />  
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />  
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />  
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />  
    <title>Weather App</title>  
  </head>  
  <body>  
    <noscript>  
      <strong  
        >We're sorry but vue-chrome-extension-tutorial-app doesn't work properly  
        without JavaScript enabled. Please enable it to continue.</strong  
      >  
    </noscript>  
    <div id="app"></div>  
    <!-- built files will be auto injected -->  
  </body>  
</html>

to replace the title.

Categories
JavaScript Answers

Using Promises for Async Programming in JavaScript

Because JavaScript is a single-threaded language, synchronous code can only be executed one line at a time. This means that if there’s synchronous code that runs longer than instantaneously, then it will stop the rest of the code from running until whatever is being run is finished. To prevent code that runs for an indeterminate amount of time from holding up other code from running, we need to have asynchronous code.


Promises

To do this in JavaScript, we can use promises in our code. Promises are objects that represent a process that runs for an indeterminate amount of time and may result in success or failure. To create promises in JavaScript, we use a Promise object that’s a constructor for creating promises. The Promise constructor takes a function called the executor function that has the resolve and reject parameters. Both parameters are functions that we call to either fulfill the promise (which means end it successfully with some value) or to reject the promise (which means returning some error value and marking the promise as failed). The function’s return value is ignored. Therefore, promises can’t return anything other than promises.

For example, we can define a promise in JavaScript like in the following code:

const promise = new Promise((resolve, reject) => {  
  setTimeout(() => resolve('abc'), 1000);  
});

The code above creates a promise that fulfills the promise with the value abc after one second. Because we’re running setTimeout inside the executor function to resolve the promise with the value abc in one second, it’s asynchronous code. We can’t return the value abc within the callback function of setTimeout, so we have to call resolve('abc') to get the resolved value back. We can access the resolved value of the fulfilled promise by using the then function. The then function takes a callback function that takes the resolved value of the fulfilled promise as a parameter. You can get the value there and do what you want with it. For example, we can do something like:

const promise = new Promise((resolve, reject) => {  
  setTimeout(() => resolve('abc'), 1000);  
});

promise.then((val) => {  
  console.log(val);  
})

When we run the code above, we should see abc logged. As we can see, the promise will supply the value when the promise is fulfilled with a call to the resolve function.

A promise has three states. It can be pending, which means that the promise has been fulfilled or rejected. It can be fulfilled, which means that the operation completed successfully. Or it can be rejected, which means that the promise operation failed.

A pending promise can either be fulfilled with a value or be rejected with some error. When a promise is fulfilled, then the corresponding resolve value is picked up by the then function and the callback function passed into the then function is called. If a promise is rejected, then we can optionally catch the error with the catch function, which also takes a callback function with the error. Both the then and catch functions return promises, so they can be chained together.

For example, we can write something like:

const promise = (num) => {  
  return new Promise((resolve, reject) => {  
    setTimeout(() => {  
      if (num === 1) {  
        resolve('resolved')  
      } else {  
        reject('rejected')  
      }  
    }, 1000);  
  });  
}

promise(1)  
  .then((val) => {  
    console.log(val);  
  })  
  .catch((error) => {  
    console.log(error);  
  })promise(2)  
  .then((val) => {  
    console.log(val);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

In the code above, we have a function promise that returns a JavaScript promise that fulfills the promise with the value resolved when num is 1, and reject the promise with error rejected otherwise. So we run:

promise(1)  
  .then((val) => {  
    console.log(val);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

Then the then function runs, the promise returned by the promise(1) function call is fulfilled since num is 1, and the resolved value is available set in val. So when we run console.log(val) we get resolved logged. When we run the code below:

promise(2)  
  .then((val) => {  
    console.log(val);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

Then the catch function runs because the promise returned by the promise(2) function call is rejected, and the rejected error value is available set in error. So when we run console.log(error) we get rejected logged.

A JavaScript promise object has the following properties: length and prototype. The length always has its value set to 1 for the number of constructor arguments, which is always one. The prototype property represents the prototype of the promise object.

A promise also has the finally method to run code that’s run no matter whether the promise is fulfilled or rejected. The finally method takes a callback function as an argument, where any code that you want to run, regardless of the outcome of the promise, is run. For example, if we run:

Promise.reject('error')  
  .then((value) => {  
    console.log(value);  
  })  
  .catch((error) => {  
    console.log(error);  
  })  
  .finally(() => {  
    console.log('finally runs');  
  })

Then we get error and finally runs logged because the original promise was rejected with the reason error. Then whatever code in the callback of the finally method runs.

The main benefit of using promises for writing asynchronous code is that we can use them to run asynchronous code sequentially. To do this, we can chain promises with the then function. The then function takes a callback function that runs when the promise is fulfilled. It also takes a second argument when the promise is rejected. To chain promises, we have to return another promise as the return value of the first callback function of the then function. It can return other values, like nothing, if we don’t want to chain another promise to the existing promise. We can return a value, which will be resolved and the value retrievable in the next then function. It can also throw an error. Then the promise returned by then gets rejected with the thrown error as the value. It can also return an already fulfilled or rejected promise, which will get the fulfilled value when a then function is chained after it or get an error reason in the callback of the catch function.

For example, we can write the following:

Promise.resolve(1)  
  .then(val => {  
    console.log(val);  
    return Promise.resolve(2)  
  })  
  .then(val => {  
    console.log(val);  
  })Promise.resolve(1)  
  .then(val => {  
    console.log(val);  
    return Promise.reject('error')  
  })  
  .then(val => {  
    console.log(val);  
  })  
  .catch(error => console.log(error));Promise.resolve(1)  
  .then(val => {  
    console.log(val);  
    throw new Error('error');  
  })  
  .then(val => {  
    console.log(val);  
  })  
  .catch(error => console.log(error));

In the first example, we chained the promises and they all resolve to a value. All the promises were already resolved into values. In the second and last examples, we reject the second promise or throw an error. They both do the same thing. The second promise is rejected, and the error reason will be logged in the callback of the catch function. We can also chain pending promises, like in the following code:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 1000);  
});

const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

promise1  
  .then(val => {  
    console.log(val);  
    return promise2;  
  })  
  .then(val => {  
    console.log(val);  
  })  
  .catch(error => console.log(error));

The callback of the first then function returned promise2, which is a pending promise.


Methods

JavaScript promises have the following methods.

Promise.all(iterable)

Promise.all takes an iterable object that lets us run multiple promises in parallel in some computers and serially in other computers. This is handy for running multiple promises that don’t depend on each other’s values. It takes in an iterable with a list of promises, usually an array, and then returns a single Promise that’s resolved when the promises that are in the iterable are resolved.

For example, we can write code like the following to run multiple promises with Promise.all:

const promise1 = Promise.resolve(1);  
const promise2 = 2;  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(3), 1000);  
});  
Promise.all([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  });

If we run the code above, then the console.log should log [1,2,3]. As we can see, it only returns a resolved value after all the promises are fulfilled. If some of them are rejected, then we won’t get any resolved value. Instead, we will get whatever error value is returned by the rejected promises. It will stop at the first rejected promise and send that value to the callback of the catch function. For example, if we have:

const promise1 = Promise.resolve(1);  
const promise2 = Promise.reject(2);  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => reject(3), 1000);  
});  
Promise.all([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })  
  .catch(error => {  
    console.log(error);  
  });

Then we get 2 logged from the console.log in the callback of the catch function.

Promise.allSettled

Promise.allSettled returns a promise that resolves after all the given promises are resolved or rejected. It takes an iterable object with a collection of promises, for example, an array of promises. The resolved value of the returned promise is an array of the final status of each promise. For example, suppose we have:

const promise1 = Promise.resolve(1);  
const promise2 = Promise.reject(2);  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => reject(3), 1000);  
});  
Promise.allSettled([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })

If we run the code above, then we get an array with three entries, with each entry being an object that has the status and value properties for the fulfilled promises and an object that has the status and reason properties for the rejected promises. For example, the code above will log {status: “fulfilled”, value: 1}, {status: “rejected”, reason: 2}, {status: “rejected”, reason: 3} . The fulfilled status is logged for the successful promises and therejected status for the rejected ones.

Promise.race

The Promise.race method returns a promise that resolves to the resolved value of the promise that’s fulfilled first. It takes an iterable object with a collection of promises, for example, an array of promises. If the iterable passed in is empty, then the returned promise will forever be pending. If the iterable object contains one or more non-promise values or already settled promises, then Promise.race will return to the first of these entries. For example, if we have:

const promise1 = Promise.resolve(1);  
const promise2 = Promise.resolve(2);  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(3), 1000);  
});  
Promise.race([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })

Then we see that 1 is logged. That’s because promise1 is the first one that’s resolved since it’s resolved instantaneously before the next lines are run. Likewise, if we have a non-promise value in the array that we pass in as the argument, like in the following code:

const promise1 = 1;  
const promise2 = Promise.resolve(2);  
const promise3 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(3), 1000);  
});  
Promise.race([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })

Then we get the same thing logged since it’s the non-promise value in the array that we pass into the Promise.race method. Synchronous code always runs before the asynchronous code, so it doesn’t matter where the synchronous code is. If we have:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
});

const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

const promise3 = 3;  
Promise.race([promise1, promise2, promise3])  
  .then((values) => {  
    console.log(values);  
  })

Then we get 3 logged since setTimeout puts the callback function in the queue to be run later, so it’s going to be run later than the synchronous code.

Finally, if we have:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
}); 
 
const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

Promise.race([promise1, promise2])  
  .then((values) => {  
    console.log(values);  
  })

Then we get 2 in the console log because a promise that’s resolved in one second is going to be resolved earlier than a promise resolved in two seconds.

Promise.reject

Promise.reject returns a promise that’s rejected with a reason. It’s useful to reject a promise with an object that’s an instance of Error. For example, if we have the following code:

Promise.reject(new Error('rejected'))  
  .then((value) => {  
    console.log(value);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

Then we get rejected logged.

Promise.resolve

Promise.resolve returns a promise that’s resolved to the value that’s passed into the argument of the resolve function. We can also pass in an object with the then property where the value of it is the callback function of a promise. If the value has a then method, then the promise will be fulfilled with the value fulfilled by the then function. That is, the first parameter of the function for the value of the then function is the same as resolve, and the second parameter is the same as reject. For example, we can write the following:

Promise.resolve(1)  
  .then((value) => {  
    console.log(value);  
  })

Then we get 1 logged since 1 is the value we passed into the resolve function to return the promise with resolved value 1.

If we pass in an object with a then method inside, like in the following code:

Promise.resolve({  
    then(resolve, reject) {  
      resolve(1);  
    }  
  })  
  .then((value) => {  
    console.log(value);  
  })

Then we get value 1 logged. That’s because the Promise.resolve function will run the then function, and the resolve parameter for the function set to the then property will be assumed to be a function that’s called the resolve function in a promise. If we instead called the reject function inside the then function in the passed-in object, then we get a rejected promise, like in the following code:

Promise.resolve({  
    then(resolve, reject) {  
      reject('error');  
    }  
  })  
  .then((value) => {  
    console.log(value);  
  })  
  .catch((error) => {  
    console.log(error);  
  })

In the code above, we get error logged since the promise is rejected.


Async and Await

With async and await, we can shorten the promise code. Before async and await, we had to use the then function and we had to put callback functions as an argument of all of our then functions. This makes the code long as we have lots of promises. Instead, we can use the async and await syntax to replace the then function and its associated callbacks. For example, we can shorten the following code from:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
});

const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

promise1  
  .then((val1) => {  
    console.log(val1);  
    return promise2;  
  })  
  .then((val2) => {  
    console.log(val2);  
  })

to:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
});  

const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(2), 1000);  
});

(async () => {  
  const val1 = await promise1;  
  console.log(val1)  
  const val2 = await promise2;  
  console.log(val2)  
})()

We replaced the then and callbacks with await. Then we can assign the resolved values of each promise as variables. Note that if we use await for our promise code, then we have to put async in the function signature as we did in the above example. To catch errors, instead of chaining the catch function at the end, we use the catch clause. Also, instead of chaining the finally function at the bottom to run code when a promise ends, we use the finally clause after the catch clause.

For example, we can write:

const promise1 = new Promise((resolve, reject) => {  
  setTimeout(() => resolve(1), 2000);  
});  
const promise2 = new Promise((resolve, reject) => {  
  setTimeout(() => reject('error'), 1000);  
});

(async () => {  
  try {  
    const val1 = await promise1;  
    console.log(val1)  
    const val2 = await promise2;  
    console.log(val2)  
  } catch (error) {  
    console.log(error)  
  } finally {  
    console.log('finally runs');  
  }})()

In the code above, we got the resolved value of the promise assigned to a variable instead of getting the value in the callback of the then function, like in the const response = await promise1 line above. Also, we used the try...catch...finally blocks to catch the errors of rejected promises and the finally clause instead of the finally function with a callback passed in to create code that runs no matter what happens to the promises.

Like any other function that uses promises, async functions always return promises and cannot return anything else. In the example above, we showed that we can chain promises in a much shorter way than with the then function with callbacks passed in as an argument.


Conclusion

With promises, we can write asynchronous code with ease. Promises are objects that represent a process that runs for an indeterminate amount of time and may result in success or failure. To create promises in JavaScript, we use a Promise object that’s a constructor for creating promises.

The Promise constructor takes a function called the executor function that has the resolve and reject parameters. Both parameters are functions that we call to either fulfill the promise (which means end it successfully with some value) or to reject the promise (which means returning some error value and marking the promise as failed). The function’s return value is ignored. Therefore, promises can’t return anything other than promises.

Promises are chainable since promises return promises. The then function of promises can throw an error, return resolved values, or return other promises that are pending, fulfilled, or rejected.

Categories
JavaScript

Top Frameworks and Libraries to Make Developing Web Apps Easier

With plain JavaScript we can do a lot of things. However, there’s no set structure or if we write plain JavaScript code, so developers need a lot of discipline to structure their code properly. This takes a lot of effort and so it wastes a lot of developer’s time enforcing structure of every part of the code.

Manipulating the DOM is also very fragile because in today’s web apps, there are way too many things going on which can change the DOM. This means that elements appear and disappear in indeterminate amount of times. This create issues since we might be trying to manipulate DOM elements that aren’t there yet. Or multiple pieces of code might try to modify the same element at the same time.

It’s just too difficult to troubleshoot those issues and get your code working correctly. This means that a lot of time on these little issues, which takes away developer’s time that can be used to develop features. This is where frameworks come in to save the day.

Frameworks provide structure which allows teams of developers to develop complex web apps in a structured way. Most frameworks enforce their own conventions so structures can be enforced if multiple developers develop the same application. DOM manipulation is abstracted away in frameworks so that developers do not have to worry about that unless they develop features that need direct DOM manipulation like animations.

Libraries are small pieces of code that we can include in our app’s code to extend the functionality of our apps. Libraries make add functionality easier and some also provide shortcuts that does the equivalent of what’s available in plain JavaScript but better. There are also libraries that are made for specific frameworks to enhance their functionality.

Vue.js

Vue.js is a component based front end JavaScript framework that lets developers create high quality maintainable web apps in an easy way. The framework lets developers divide their app into Vue.js components, which are self contained parts that when put together, we get a full web app. Components are made to be nested within each other. Each component has a template for rendering the data, a script section for the logic, and a styles section for the CSS styles. Component support data binding between the template and script sections which means that changes from the template section is automatically reflected in the script section and vice versa. Each component has its own life cycle and has function to handle those life cycle events. Parent components can pass data into child components and child component can emit events to pass components into the parent.

With Vue.js, DOM manipulation is done in the background by getting the difference between the browser DOM and the virtual DOM tree it creates and make the changes from the virtual DOM to the actual DOM.

Also there’s a built in URL router called the Vue Router that lets developers map specific URLs into the pages of their apps. For centralize state management it uses the Vuex flux library which lets us use the flux architecture to store the app’s state centrally. Flux architecture basically means that data are set in a central data store and then components that need the data always get the latest data from the store by observing the values from the store.

New apps usually use the Vue CLI to create the project and select what to include.

Other feature it supports out of the box includes testing, CSS preprocessors like SCSS and SASS to make writing complex CSS easier, and building to web components or progressive web apps, and other advanced features. It can also be added incrementally to legacy web apps by using script tags. The Vue.js framework and libraries made for Vue.js all support this. So it’s a great choice for upgrading the functionality of legacy web apps.

There’s large ecosystem of libraries that we can use with Vue.js applications including component libraries, helper modules geolocation and webRTC modules and many others.

Angular

Like Vue.js, Angular is a component based framework. It’s made by Google. Most of the things that’s part of the Vue.js feature set is also in Angular, except the state management part. By default, Angular applications are written in TypeScript he framework lets developers divide their app into Angular components, which are self contained parts that when put together, we get a full web app. Components are made to be nested within each other. Each component has a template file for rendering the data, a TypeScript file for the logic, and a styles file for styling, which can be CSS, SASS or SCSS. Also, there’s a test file to add your unit tests for each component. Component support data binding between the template and script sections which means that changes from the template section is automatically reflected in the TypeScript file and vice versa. Each component has its own life cycle and has function to handle those life cycle events. Parent components can pass data into child components and child component can emit events to pass components into the parent.

Many parts of the library uses reactive programming, which means that parts of the app watches for data changes and does something when it changes. This is done mainly with observables.

One unique feature of Angular is that it includes dependency injection so that we don’t have to worry about resolving the dependencies of Angular libraries ourselves, which saves a lot of headaches since most web apps probably lots of dependencies.

Angular apps are divided into modules, which we can combine into one to make one whole app. Each module include the libraries that are needed to be used by that module.

With Angular, DOM manipulation is done in the background by getting the difference between the browser DOM and the virtual DOM tree it creates and make the changes from the virtual DOM to the actual DOM.

Also there’s a built in URL router called the Angular Router that lets developers map specific URLs into the pages of their apps.

Other feature it supports out of the box includes building to web components or progressive web apps, and other advanced features. There’s large ecosystem of libraries that we can use with React applications including component libraries, helper modules like NgRx store for state management many others.

React

React is a component based library that we can use to make dynamic web apps. It uses the JSX syntax for writing JavaScript which looks like HTML, except that you can mix it with JavaScript directly. Each component has its own life cycle and has function to handle those life cycle events. It also provides nesting for components and also allow for higher order components, which are components that returns component. There are 2 kinds of components in React. One is class based components, which are written as a class, with a render method at the end to render the HTML. The other is function components which returns the JSX directly and rendered into HTML. Originally function components cannot have dynamic logic, but now that React has hooks, function components can also have logic code in it.

With React, DOM manipulation is done in the background by getting the difference between the browser DOM and the virtual DOM tree it creates and make the changes from the virtual DOM to the actual DOM.

It does not include anything other than a view library to render JavaScript into HTML, so if we want to build a full single page application, we need to add other libraries like React Router and a state management like Redux. There’s large ecosystem of libraries that we can use with React applications including component libraries, helper modules like React Router and Redux and many others.

Lodash

Lodash is a library which contains many handy methods for manipulating data. Examples include array methods like flatten to squash a nested array into an ordinary array. There’s also a flattenDeep to flatten all levels of a nested array into a flat array. Other functions include:

  • findIndex — find the index of a given object
  • findLastIndex — find the last index of a given object
  • pull — removes the first given item from the array
  • pullAll — remove all the given item from the array
  • reverse — reverse an array
  • sortedIndex — get the index to insert the given item so that the array stays sorted
  • get — function used to traverse an object to get the item with the given property and return null if it can’t traverse the object to get the item or if it’s null or undefined

There are many more functions that aren’t yet available in plain JavaScript, even though some like find and findIndex did make it into JavaScript’s standard library, so Lodash is still useful.

Moment.js

Manipulating time and date in JavaScript is painful. Even with the functions available with Date objects, there are plenty of chances of bugs and errors. Also, there are no functions for formatting times and dates which is a big problem. To solve this, we use moment.js. This is the best library available for dealing with time.

There were issues with time zones with YYYY-MM-DD dates being parsed into UTC time as opposed to local time. Which creates bugs for developers who aren’t aware of this problem. See https://stackoverflow.com/questions/29174810/javascript-date-timezone-issue

Also there are difference in support for parts of Dates in different browsers. (See https://stackoverflow.com/questions/11253351/javascript-date-object-issue-in-safari-and-ie)

It is also hard to add and subtract timestamps with built in Date functions. There is no way to do this without writing a lot of code and doing a lot of checks. Also, there is no way to compare 2 times.

Formatting dates is also not available without writing your own code for using third party libraries.

Moment.js solves all of these issues by providing built in functions to do all these common operations. It provides functions for parsing and formatting dates.

The momentconstructor is where you can pass in a date string, and a moment object will be created. For example, you can pass in:

moment('2019-08-04')

and you will get back a moment which you can compare with other moment objects, and add or subtract by different time spans.

If you do not passing in anything to the moment constructor, you get the current date and time.

It also takes a second argument. If you want to make sure a date is parsed as a YYYY-MM-DD date, then write moment(‘2019–08–04’, 'YYYY-MM-DD') . If you don’t know the format of your date or time, then you can pass in an array of possible formats and Moment will pick the right one:

moment('2019–08–04', ['YYYY-MM-DD', 'DD-MM-YYYY']);

After you create a Moment object, then you can do many things like formatting dates:

const a = moment('2019–08–04', 'YYYY-MM-DD').format('MMMM Do YYYY, h:mm:ss a');
console.log(a);// August 4th 2019, 12:00:00 am

const b = moment('2019–08–04', 'YYYY-MM-DD').format('dddd');
console.log(b);// Sunday

const c = moment('2019–08–04', 'YYYY-MM-DD').format("MMM Do YY");
console.log(c);// Aug 4th 19

const d = moment('2019–08–04', 'YYYY-MM-DD').format('YYYY [escaped] YYYY');
console.log(d);// 2019 escaped 2019

const e = moment('2019–08–04', 'YYYY-MM-DD').format();
console.log(e);// 2019-08-04T00:00:00-07:00

From the above examples, we see that we can format dates in pretty much any way we want.

We can also tell what time span a date is relative to another date by writing:

const augDate = moment('2019–08–04', 'YYYY-MM-DD');
const sepDate = moment('2019–09–04', 'YYYY-MM-DD');console.log(augDate.from(sepDate)); // a month ago

We can also add or subtract Moment dates:

const augDate = moment('2019–08–04', 'YYYY-MM-DD');
const sepDate = moment('2019–09–04', 'YYYY-MM-DD');console.log(augDate.add(10, 'days').calendar()); // 08/14/2019
console.log(augDate.subtract(10, 'days').calendar()); // 07/25/2019

It is easy to compare 2 dates

moment('2010-01-01').isSame('2010-01-01', 'month'); // true
moment('2010-01-01').isSame('2010-05-01', 'day');   // false, different month
moment('2008-01-01').isSame('2011-01-01', 'month'); // false, different year

You can also check if a date is has Daylight Saving Time in effect or not:

const augDate = moment('2019–08–04', 'YYYY-MM-DD');
const decDate = moment('2019–12–04', 'YYYY-MM-DD');
console.log(augDate.isDST()) // true
console.log(decDate.isDST()) // false

And you can convert back to JavaScript date any time by calling the toDate() function on a Moment object.

As we can see. There’re lots of things that Moment.js can do that can’t be done in plain JavaScript, so we need this library badly.

All in all, developing front end JavaScript web apps feels almost like magic with all these libraries and frameworks. It makes development a lot easier if you’re working alone and in teams. There’s no way to live without all these modern day libraries and frameworks. A lot of these things that frameworks and libraries do aren’t possible with plain JavaScript.

Categories
JavaScript JavaScript Basics

Using JavaScript BigInt to Represent Large Numbers

To represent integers larger than 2**53 – 1 in JavaScript, we can use the BigInt object to represent the values.

It can be manipulated via normal operations like arithmetic operators — addition, subtraction, multiplication, division, remainder, and exponentiation.

It can be constructed from numbers and hexadecimal or binary strings. Also, it supports bitwise operations like AND, OR, NOT, and XOR. The only bitwise operation that doesn’t work is the zero-fill right-shift operator (>>> ) because BigInts are all signed.

Also, the unary + operator isn’t supported to not break asm.js. These operations are only done when all the operands are BigInts. We can’t have some operands being BigInts and some being numbers.

In JavaScript, a BigInt is not the same as a normal number. It’s distinguished from a normal number by having an n at the end of the number. We can define a BigInt with the BigInt factory function.

It takes one argument that can be an integer number or a string representing a decimal integer, hexadecimal string, or binary string. BigInt cannot be used with the built-in Math object.

Also, when converting between number and BigInt and vice versa, we have to be careful because the precision of BigInt might be lost when a BigInt is converted into a number.


Usage

To define a BigInt, we can write the following if we want to pass in a whole number:

const bigInt = BigInt(1);  
console.log(bigInt);

It would log 1n when we run the console.log statement. If we want to pass a string into the factory function instead, we can write:

const bigInt = BigInt('2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222');  
console.log(bigInt);

We can also pass an hexadecimal number string with a string that starts with 0x into the factory function:

const bigHex = BigInt("0x1fffffffffffff111111111");  
console.log(bigHex);

The code above would log 618970019642690073311383825n when we run console.log on bigHex.

Likewise, we can pass in a binary string with a string that starts with 0b and binary string in the remainder of the string to get a BigInt:

const bigBin = BigInt("0b111111111111111000000000011111111111111111111111");  
console.log(bigBin);

The code above would get us 281466395164671n when we run console.log on it. Passing in strings would be handy if the BigInt that we want to create is outside of the range that can be accepted by the number type.

We can also define a BigInt with a BigInt literal, we can just attach an n character to the whole number. For example, we can write:

const bigInt = 22222222222222222222222222222222n;  
console.log(bigInt);

Then, we get 22222222222222222222222222222222n as the value of bigInt when we log the value.

BigInt has its own data type in JavaScript. When you run the typeof operator on a BigInt variable, constant or value, we would get bigint. For example, when we run:

typeof 2n === 'bigint';  
typeof BigInt(2) === 'bigint';

Both lines of code would evaluate to true. However, if we wrap it with the Object factory function, we get that the type is an object:

typeof Object(2n) === 'object';

The code above would evaluate to true.

We can apply number operations to BigInts.

This includes the arithmetic operators — addition, subtraction, multiplication, division, remainder, and exponentiation. Also, it supports bitwise operations like AND, OR, NOT, and XOR.

The only bitwise operation that doesn’t work is the zero-fill right-shift operator (>>> ) because BigInts are all signed. Also, the unary + operator isn’t supported to not break asm.js.

These operations are only done when all the operands are BigInts. We can’t have some operands being BigInts and some being numbers. For example, if we have:

const bigInt = BigInt(Number.MAX_SAFE_INTEGER);  
console.log(bigInt);
const biggerInt = bigInt + BigInt(1);  
console.log(biggerInt);
const evenBiggerInt = bigInt + BigInt(2);  
console.log(evenBiggerInt);
const multi = bigInt * BigInt(2);  
console.log(multi);
const subtr = bigInt - BigInt(10);  
console.log(subtr);
const remainder = bigInt % BigInt(1);  
console.log(remainder);
const bigN = bigInt ** BigInt(54);  
console.log(bigN);
const veryNegativeNum = bigN * -BigInt(1)  
console.log(veryNegativeNum);

We get 9007199254740991n for bigInt, 9007199254740992n for biggerInt, 9007199254740993n for evenBiggerInt, 18014398509481982n for multi, 9007199254740981n for subtr, 0n for remainder, 3530592467533273200243077170885155617107348521747142286627863260349958518655132050034081285541183004983189865471543609006121689601641796259277395721973496380268998810860889580999688899063966604079229944616948651888866122410855207004436640389001057851295873774080462273415460559916461808220601907673652198075821210257903676343961872269549414664419834643799298966710366275919846143068708381391506113181640387818197335712192797007703730122048543818655729529755334964590919971124632271934272078761071878238334159341746985273963326734748343552398522547662400805644304911487571159654254814460707275228515191584712593238883953404971043549757327554636466197405269908317698331974392008288867249576664013677011521696874812515379689360830743272800013459321098384864332719963035293422648481458040217707301509007592199565531403472471705983351384755965631442881685949576642561n for bigN, and -3530592467533273200243077170885155617107348521747142286627863260349958518655132050034081285541183004983189865471543609006121689601641796259277395721973496380268998810860889580999688899063966604079229944616948651888866122410855207004436640389001057851295873774080462273415460559916461808220601907673652198075821210257903676343961872269549414664419834643799298966710366275919846143068708381391506113181640387818197335712192797007703730122048543818655729529755334964590919971124632271934272078761071878238334159341746985273963326734748343552398522547662400805644304911487571159654254814460707275228515191584712593238883953404971043549757327554636466197405269908317698331974392008288867249576664013677011521696874812515379689360830743272800013459321098384864332719963035293422648481458040217707301509007592199565531403472471705983351384755965631442881685949576642561n for veryNegativeNum.

Note that when we get fractional results, the fractional parts will be truncated when result has it. BigInt is a big integer and it’s not for storing decimals. For example, in the examples below:

const expected = 8n / 2n;  
console.log(expected)const rounded = 9n / 2n;  
console.log(rounded)

We get 4n for expected and rounded. This is because the fractional part is removed from the BigInt.

Comparison operators can be applied to BigInts. The operands can be either BigInt or numbers. We can use the bigger than, bigger than or equal to, less than, less than or equal to, double equals, and triple equals operators with BigInts.

For example, we can compare 1n to 1:

1n === 1

The code above would evaluate to false because BigInt and numbers aren’t the same types. But when we replace triple equals with double equals, like in the code below:

1n == 1

The statement above evaluates to true because only the value is compared.

Note that in both examples, we mixed BigInt operands with number operands. This is allowed for comparison operators.

BigInts and numbers can be compared together with other operators as well, like in the following examples:

1n < 9  
// true9n > 1  
// true9 > 9n  
// false  
  
9n > 9  
// false  
  
9n >= 9  
// true

Also, they can be mixed together in one array and sorted together. For example, if we have the following code:

const mixedNums = [5n, 6, -120n, 12, 24, 0, 0n];  
mixedNums.sort();  
console.log(mixedNums)

We get [-120n, 0, 0n, 12, 24, 5n, 6]. We also sort it in descending order with the following code:

const mixedNums = [5n, 6, -120n, 12, 24, 0, 0n];  
mixedNums.sort((a, b) => {  
  if (a > b) {  
    return -1  
  } else if (a < b) {  
    return 1  
  }  
  return 0  
});  
console.log(mixedNums)

In the code above, we used the comparison operators to compare the value of the numbers and return -1 if the first number is bigger than the second number, return 1 if the first number is less than the second number, and return 0 otherwise.

If we wrap BigInts with Object, then they’re compared as objects, so two objects are only considered the same if the same instance is referenced. For example, if we have:

0n === Object(0n);  
Object(0n) === Object(0n);  
const o = Object(0n);  
o === o;

Then the first three lines in the code above would evaluate to false while the last line would evaluate to true.

When BigInts are coerced into booleans, they act the same as if they’re numbers. For example, Boolean(0n) would return false, and anything else would return true.

For example, we can coerce them into booleans like in the following code:

0n || 11n  
// 11n  
  
0n && 11n  
// 0n  
  
Boolean(0n)  
// false  
  
Boolean(11n)  
// true  
  
!11n  
// false  
  
!0n  
// true

BigInt Methods

The BigInt object has several methods. There are the static asIntN() and asUintN() methods, and the toLocaleString(), toString(), and valueOf() instance methods.

The asIntN method wraps a BigInt value between -2 to the width minus 1 and 2 to the width minus 1. For example, if we have:

const bigNum = 2n ** (62n - 1n) - 1n;  
console.log(BigInt.asIntN(62, bigNum));

Then we get the literal of the actual value of bigNum modulo 62 returned, which is 2305843009213693951n. However, if we have:

const bigNum = 2n ** (63n - 1n) - 1n;  
console.log(BigInt.asIntN(62, bigNum));

We get -1n since the 2n ** (63n — 1n) modulo 2n ** 63n is returned.

The asUintN() method wraps a BigInt value between 0 and 2 to the width minus 1. For example, if we have:

const bigNum = 2n ** 11n - 1n;  
console.log(BigInt.asUintN(11, bigNum));

Then we get the literal of the actual value of bigNum returned, which is 2305843009213693951n. However, if we have:

const bigNum = 2n ** 11n - 1n;  
console.log(BigInt.asUintN(11, bigNum));

We get 2047n since the 2n ** 11n — 1n modulo 2n ** 11n is returned.

BigInt has a toLocaleString() method to return the value of the string for the BigInt depending on the locale we pass in. For example, if we want to get the French representation of a BigInt, we can write:

const bigNum = 2n ** 60n - 1n;  
console.log(bigNum.toLocaleString('fr'));

The code above will log 1 152 921 504 606 846 975.

The toString() method will convert a BigInt to a string. For example, we can write:

const bigNum = 2n ** 60n - 1n;  
console.log(bigNum.toString());

The code above will log 1152921504606846975.

The valueOf method will get the value of the BigInt object. For example, if we run:

const bigNum = 2n ** 60n - 1n;  
console.log(bigNum.valueOf());

Then we get 1152921504606846975n logged.

BigInts aren’t supported by JSON, so a TypeError would be raised if we try to convert it to JSON with JSON.stringify().

However, we can convert it to something supported like a string, then it can be stored as JSON. We can override the toJSON method of a BigInt by writing:

BigInt.prototype.toJSON = function() {  
  return this.toString();  
}

const bigIntString = JSON.stringify(88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888n)  
console.log(bigIntString);

Then, we get: “88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888”.

BigInts are useful for representing integers larger than 2 to the 53rd power minus 1 in JavaScript, we can use the BigInt object to represent the values.

It can be manipulated via normal operations like arithmetic operators — addition, subtraction, multiplication, division, remainder, and exponentiation.

BigInts support most bitwise operations like AND, OR, NOT, and XOR.

They can also be converted from hexadecimal or binary numbers.

These operations are only done when all the operands are BigInts. We can’t have some operands being BigInts and some being numbers.

In JavaScript, a BigInt is not the same as a normal number. It’s distinguished from a normal number by having an n at the end of the number. We can define a BigInt with the BigInt factory function.

It takes one argument that can be an integer number or a string representing a decimal integer, hexadecimal string, or binary string. BigInt cannot be used with the built-in Math object.

Categories
JavaScript Basics

Easily Iterate Over JavaScript Collections with the For-Of Loop

Starting with ES2015, we have a new kind of loop to loop over iterable objects. The new for…of loop is a new kind of loop that lets us…

Starting with ES2015, we have a new kind of loop to loop over iterable objects. The new for...of loop is a new kind of loop that lets us loop over any iterable objects without using a regular for loop, while loop, or using the forEach function in the case of arrays. It can be used directly to iterate through any iterable objects, which include built in objects like Strings, Arrays, array-like objects like arguments and NodeList , TypedArray , Map , Set and any user-defined iterables. User-defined iterables include entities like generators and iterators.

If we want to use the for...of loop to iterate over an iterable object, we can write it with the following syntax:

for (variable of iterable){
  // run code
}

The variable in the code above is the variable representing each entry of the iterable object that are being iterated over. It can be declared with const , let or var . The iterable is the object where the properties are being iterated over.

For example, we can use it to iterate over an array like in the following code:

const arr = [1,2,3];

for (const num of arr) {
  console.log(num);
}

The code above, the console.log statements will log 1, 2, and 3. We can replace const with let if we want to assign the variable we used for iteration in the for...of loop. For example, we can write:

const arr = [1,2,3];

for (let num of arr) {
  num *= 2 ;
  console.log(num);
}

The code above, the console.log statements will log 2, 4, and 6 since we used the let keyword to declare the num so we can modify num in place by multiplying each entry by 2. We cannot reassign with const so we have to use let or var to declare the variable we want to modify in each iteration.

We can also iterate over strings. If we do that we all get each character of the string in each iteration. For example, if we have the code below:

const str = 'string';

for (const char of str) {
  console.log(char);
}

Then we get the individual characters of 'string' logged in each line.

Likewise, we can iterate over TypedArrays, which contains binary data represented by a series of numbers in hexadecimal format. For example, we can write the following code:

const arr = new Uint8Array([0x00, 0x2f]);`

for (const num of arr) {
  console.log(num);
}`

In the example above, console.log will log 0 and 47. Note that the logged value is in decimal format but the entered value is in hexadecimal format.

If we iterate over Maps, then we get each entry of the Map. For example, we can write the following code to iterate over Maps:

const map = new Map([['a', 2], ['b', 4], ['c', 6]]);

for (const entry of map) {
  console.log(entry);
}

If we log the entries, we get ['a', 2], ['b', 4], and ['c', 6] . Maps consists of key-value pairs as their entries. When we iterate over a Map, we get the key as the first element and the value as the second element is each entry. To get the key and value of each entry into its own variable we can use the destructuring operator, like in the following code:

const map = new Map([['a', 2], ['b', 4], ['c', 6]]);

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

Then when we log the entries, we get 'a' 2, 'b' 4, and 'c' 6 .

We can also use the for...of loop for Sets. For example, we can loop over a Set by doing the following:

const set = new Set([1, 1, 2, 3, 3, 4, 5, 5, 6]);

for (const value of set) {
  console.log(value);
}

We set that we get 1, 2, 3, 4, 5, and 6 logged since the Set constructor automatically eliminates duplicate entries by keeping the first occurrence a value in the Set and discarding the later occurrence of the same value.

The for...of loop also works for iterating over the arguments object, which is an global object that has the arguments that were passed into the function when the function is called. For example, if we write the following code:

(function() {
  for (const argument of arguments) {
    console.log(argument);
  }
})(1, 2, 3, 4, 5, 6);

We see that we see 1, 2, 3, 4, 5, and 6 logged since this is what we passed in when we called the function. Note that this only works for regular functions since the context of this has to be changed to the function being called instead of window . Arrow functions doesn’t change the content of this , so we won’t get the correct arguments when we run the same loop inside an arrow function.

Also, we can iterate over a list of DOM Node objects, called a NodeList . For example, is a browser implemented the NodeList.prototype[Symbol.iterator] , then we can use the for...of loop like in the following code:

const divs = document.querySelectorAll('div');

for (const div of divs) {
  console.log(div);
}

In the code above we logged all the div elements that are in the document.

With for...of loops, we can end the loop by using the break , throw or return statements. The iterator will close in this case, but the execution will continue outside the loop. For example, if we write:

function* foo(){
  yield 'a';
  yield 'b';
  yield 'c';
};

for (const o of foo()) {
  console.log(o);
  break;
}

console.log('finished');

In the code above, we only log ‘a’ because we have a break statement at the end of the for...of loop, so after the first iteration, the iterator will close and the loop ends.

We can loop over generators, which are special functions that returns a generator function. The generator function returns the next value of an iterable object. It’s used for letting us iterate through a collection of objects by using the generator function in a for...of loop.

We can also loop over a generator that generate infinite values. We can have an infinite loop inside the generator to keep returning new values. Because the yield statement doesn’t run until the next value is requested, we can keep an infinite loop running without crashing the browser. For example, we can write:

function* genNum() {
  let index = 0;
  while (true) {
    yield index += 2;
  }
}

const gen = genNum();
for (const num of gen) {
  console.log(num);
  if (num >= 1000) {
    break;
  }
}

If we run the code above, we see that we get numbers from 2 to 1000 logged. Then num is bigger than 1000, so that the break statement is ran. We cannot reuse the generator after it’s closed, so if we write something like the following:

function* genNum() {
  let index = 0;
  while (true) {
    yield index += 2;
  }
}

const gen = genNum();
for (const num of gen) {
  console.log(num);
  if (num >= 1000) {
    break;
  }
}

for (const num of gen) {
  console.log(num);
  if (num >= 2000) {
    break;
  }
}

The second loop won’t run because the iterator that was generated by the generator is already closed by the first loop with the break statement.

We can iterate over other iterable objects that have the method denoted with the Symbol.iterator Symbol defined. For example, if we have the following iterable object defined:

const numsIterable = {
  [Symbol.iterator]() {
    return {
      index: 0,
      next() {
        if (this.index < 10) {
          return {
            value: this.index++,
            done: false
          };
        }
        return {
          value: undefined,
          done: true
        };
      }
    };
  }
};

Then we can run the loop below to show log the generated results:

for (const value of numsIterable) {
  console.log(value);
}

When we run it, should see 0 to 9 logged when console.log is run in the loop above.

It’s important that we don’t confuse the for...of loop with the for...in loop. The for...in loop if for iterating over the top-level keys of objects including anything up the prototype chain, while the for...of loop can loop over any iterable object like arrays, Sets, Maps, the arguments object, the NodeList object, and any user-defined iterable objects.

For example, if we have something like:

Object.prototype.objProp = function() {};
Array.prototype.arrProp = function() {};

const arr = [1, 2, 3];
arr.foo = 'abc';

for (const x in arr) {
  console.log(x);
}

Then we get 0, 1, 2, ‘foo’, ‘arrProp’ and ‘objProp’ logged, which are keys of objects and methods that are defined for the arr object. It included all properties and methods up the prototype chain. It inherited all properties and methods from Object and Array that were added to Object and Array’s prototype so we get all the things in the chain inheritance in the for...in loop. Only enumerable properties are logged in the arr object in arbitrary order. It logs index and properties we defined in Object and Array like objProp and arrProp .

To only loop through properties that aren’t inheritance from an object’s prototype, we can use the hasOwnPropetty to check if the property is defined on the own object:

Object.prototype.objProp = function() {};
Array.prototype.arrProp = function() {};

const arr = [1, 2, 3];
arr.foo = 'abc';

for (const x in arr) {
  if (arr.hasOwnProperty(x)){
    console.log(x);
  }
}

objProp and arrProp are omitted because they’re they’re inherited from Object and Array objects respectively.

The for...of loop is a new kind of loop that lets us loop over any iterable objects without using a regular for loop, while loop, or using the forEach function in the case of arrays. It can be used directly to iterate through any iterable objects, which include built in objects like Strings, Arrays, array-like objects like arguments and NodeList , TypedArray , Map , Set and any user-defined iterables. User-defined iterables include entities like generators and iterators. This is a handy loop because it lets us over any iterable object rather than just arrays. Now we have a loop statement that works with iterable object.