[en] Javascript tricks #1 – extendings prototypes

Polish version

Javascripts gives us lots of methods to perform operations on arrays in functional style, such as filter, map, reduce etc.

const input=[
    {name: "John", points: 5},
    {name: "Bob", points: 0},
    {name: "Alice", points: 4}];
console.log(input.filter(x=>x.points>0).map(x=>x.name));
// => ["John", "Alice"]

But capabilities of this built-in methods are not so powerful like for example in C# and .Net.

There are libraries designed for this purpose. For example Lodash has a lot of functions:

var _ = require('lodash');

const input=[
    {name: "John", points: 5},
    {name: "Bob", points: 0},
    {name: "Alice", points: 4}];
console.log(_.map(_.filter(input, x=>x.points>0),x=>x.name));
// => ["John", "Alice"]

As you see, this method are not invoked on arras but gets arrays as a parameter. If you want to chain methods (like in example, filter and there map) code starts to be less readable – name of method map is on beginning of line, but it’s method – on end.

Extensions

In C# there is something called ‘extension method’. This is method, that is added to class outside it.

public static class A{
    public static string MyMethod(this List<string> a){
        return "Hello, world!";
    }
}

//...

var animals = new List<string>{"cat", "dog", "lion"};
Console.WriteLine(animals.MyMethod());

You can do very similar think in JS. Javascript uses prototype-based inheritance, so all arrays (not every array-like object is array) inherit from Array.prototype. It is object built-in browser, but you can add to it properties and methods like to any other objects.

const animals = ["cat", "dog", "lion"];
Array.prototype.myMethod=function(){return "Hello, world!";}
console.log(animals.myMethod());// => "Hello, world!"

Now many experienced JS developers probably treats me like heretic. It is because main difference between this and C# extension method is that in C# extension methods exist in some namespace and it is visible only after importing this namespace. If you edit prototype in JS, you do it globally (not only for current scope, but for all arrays). It is called prototype pollution.

So be very careful with it. If you have project written by more than you – please inform other programmers what you doing. And I advise against doing this in libraries.

What methods you can add?

I will show you some examples of useful methods you can add. If you want, you can download npm library I created (but I encourage you to write yourself)

npm i prototype-extensions
# OR
yarn add prototype-extensions

sum

First of methods simply returns one number, that is sum of all items.

const array=[10, 2, 5, 0.5];
console.log(array.sum());// => 17.5

Sometimes we don’t have plain numbers, but complex objects. That is why a good idea it pass as argument function, that will extract plain number for us.

const work=[
{worket: 'Alice', workedHours:8, pricePerHour:20},
{worket: 'Bob', workedHours:10, pricePerHour:15},
{worket: 'John', workedHours:2, pricePerHour:30},
];
const totalCost = work.sum(x=>x.workedHours * pricePerHour);
console.log(totalCost);// => 370

Implementation

if (!Array.prototype.sum) {
    Array.prototype.sum = function (fun = x => x) {
        return this.reduce((sum, item) => sum + Number(fun(item)), 0);
    }
}

First if is to ensure, that we don’t override any already existing method.

At second line we create new function and assign to prototype of arrays. As parameter we take other function, that we will execute on each array of item. Default value of this parameter is function, that do nothing. If this syntax is unclear for you, here is other version that do the same

 Array.prototype.sum = function (fun) {
    if(!fun){
        fun = function(input){
            return input;
        }
    }

sortBy

It is very common think to sort. But in built-in js method require function that compare 2 objects and return if first is bigger, smaller or equal. It will be much more convenient to just sort by value (or multiple values)

const people=[
    {name:'Anna', age:18},
    {name:'Bob', age:25},
    {name:'John', age:17},
];
people.sortBy(x=>x.age);
console.log(people);// => [
                    // =>     {name:'John', age:17},
                    // =>     {name:'Anna', age:18},
                    // =>     {name:'Bob', age:25},
                    // => ];

Implementation

if (!Array.prototype.sortBy) {
    Array.prototype.sortBy = function (...args) {
        let orders = args.map(x => typeof x == 'function' ? x : y => y[x]);
        let compareFunction = (a, b) => {
            for (let order of orders) {
                let valueA = order(a);
                let valueB = order(b);
                if (valueA > valueB)
                    return 1;
                else if (valueA < valueB)
                    return -1;
            }
            return 0;
        };
        return this.sort(compareFunction);
    }
}

I assumed, that you could want to order by many values (if some values were equal sort by next). That’s why I used …args notation (it creates an array of function’s arguments) and used for..of loop.

I also assumed, that you could as argument put not only method, but also string with name of property (that is why I put map function)

min & max

How to find biggest or lowest element of array? You could sort it, and then get first/last element, but this solution has one problem – performance. it’s much much quicker to make method special for finding one minimum/maximum element.

const array1 = [5,10,2.2,-30];
console.log(array1.min()); // => -30
console.log(array1.max()); // => 10

But in objective world, it’s rare to have arrays of plain numbers.

const people=[
    {name:'Anna', age:18},
    {name:'Bob', age:25},
    {name:'John', age:17},
];
console.log(people.min(x=>x.age));// => {name:'John', age:17}

Implementation

if (!Array.prototype.max) {
    Array.prototype.max = function (fun = x => x) {
        let value = null;
        let object = null;
        for (let item of this) {
            const itemValue = fun(item);
            if (typeof itemValue === 'number' && !isNaN(itemValue) && (value == null || itemValue > value)) {
                value = itemValue;
                object = item;
            }
        }
        return object;
    }
}

groupBy

Sometimes you want to group some objects by common property:

const people=[
    {name:'Alice', type:'teacher'},
    {name:'Bob', type:'student'},
    {name:'John', type:'student'}
];
const grouped = people.groupBy(x => x.type);
console.log(grouped); // => Map(2){
                      // =>  "teacher" => [{name:'Alice', type:'teacher'}],
                      // =>  "student" => [{name:'Bob', type:'student'}, {name:'John', type:'student'}]
                      // => }

You can ask, why I return Map instead of simple Object? It’s because in Map key could be everything, not only strings and symbols, so this gives us more flexibility.

Implementation

if (!Array.prototype.groupBy) {
    Array.prototype.groupBy = function (fun = x => x) {
        const ret = new Map();
        for (const value of this) {
            const key = fun(value);
            if (ret.has(key))
                ret.get(key).push(value);
            else
                ret.set(key, [value]);
        }
        return ret;
    }
}

Future

In ECMAScript Next specification there is proposed bind operator :: ( https://github.com/tc39/proposal-bind-operator) which would be better solution that extending prototypes. But it is on stage 0, which means it isn’t sure that it will be added to JavaScript specification, and even if, it would take lots of time until browsers will implement this.

Read more:

[en] Promises in Vue – how to do api requests correctly

Polish version

On this article I assume, that you have knowlege about promises in JS. If not, read MDN article.

Look at this vue component and try to find bug in it:

<template>
    <div>
        <button @click="load(1)" :class="{active:active===1}">Show first article</button>
        <button @click="load(2)" :class="{active:active===2}">Show second article</button>
        <p v-if="loader">Loading...</p>
        <p v-else>{{content}}</p>
    </div>
</template>
<script>
export default {
    data(){
        return {content:null, active:null, loader:false}
    },
    methods:{
        load(id){
            this.active=id;
            this.loader=true;
            fetch(`/article/${id}`)
                .then(response=>response.json())
                .then(json=>{
                    this.content=json;
                    this.loader=false;
                });
        }
    }
};
</script>
<style>
    button.active{
        color:red;
    }
</style>

It is simple component, that has 2 buttons. After clicking on button it loads content from rest api and adds class active so you can see which article is opened. It even has message ‘Loading…’ until content was loaded. It is so simple, what could be wrong here?

But what will it do, when you try click both buttons quickly? Probably nothing, because you probably testing it on localhost, so api responds faster that you click. And even if it is slower (or you simulate slow network in dev tools) probably nothing happens.

It is because server responses in the same order that you clicked. But it is possible, that processing of first request will take more time, that the second. Then your content variable don’t match active variable, so for user content of article don’t match selected button.

You can think, so if works correctly most of time, that it is not worth to carry. But in realiy this is the worst kind of bug: user/customer says, taht sometimes your softwarre is buggy, but when you tries, it works complettly fine. But customer don’t stop complaining.

My method how to reproduce such bug is to add random sleep to all requests on server, so all responses will be received in random order.

How to fix it

There are many ways to fix it. The simplest could be, to disable buttons when loader is true. But this isn’t user-friendly and for me it is hiding bug, not fixing.

Second idea is to test if this request is still valid

        load(id){
            this.active=id;
            this.loader=true;
            fetch(`/article/${id}`)
                .then(response=>response.json())
                .then(json=>{
                    if(this.active!=id) return;//id changed, so ignore
                    this.content=json;
                    this.loader=false;
                });
        }

In this example we tested if id was changed, because we assume that if we ask api multiple times for the same id we get the same response. If we can’t assume that, use Symbol.

        load(id){
            const loadSymbol = Symbol();
            this.loadSymbol = loadSymbol;
            this.active=id;
            this.loader=true;
            fetch(`/article/${id}`)
                .then(response=>response.json())
                .then(json=>{
                    if(this.loadSymbol != loadSymbol) return;//loadSymbol changed, so ignore
                    this.content=json;
                    this.loader=false;
                });
        }

Promise Status

Let’s look at this class:

import {Enum} from 'enumify-fork';

export default class PromiseStatus {
    constructor(promise = null) {
        this.promise = promise;
    }

    set promise(promise) {
        this.data = null;
        this.error = null;
        this.status = PromiseStatus.Status.noPromise;
        this._promise = null;
        if (promise) {
            this._promise = promise;
            this.status = PromiseStatus.Status.pending;
            promise.then(data => {
                if (this._promise === promise) {
                    this.data = data;
                    this.status = PromiseStatus.Status.resolved;
                }
            }, error => {
                if (this._promise === promise) {
                    this.error = error;
                    this.status = PromiseStatus.Status.resolved;
                }
            })
        }
    }

    get promise() {
        return this._promise;
    }
}

PromiseStatus.Status = class extends Enum {
};
PromiseStatus.Status.initEnum(['noPromise', 'pending', 'resolved', 'rejected']);

You provide promise to it, and it gives you property status, which informs you if promise is pending (data are still loading), resolved (data loaded successfully) of rejected (some error). data and error contains variables provided by resolve and reject respectively.

But what is the difference between this and standard .then().catch()? Answer is, that you can use this properties directly in vue template (for example in v-if) or computed properties, and your html will be rerendered automatically.

You can download this class from npm/yarn. Link to github repo: https://github.com/matrix0123456789/reactive-promise-status

yarn add reactive-promise-status
OR
npm install reactive-promise-status

How to use it?

<template>
    <div>
        <button @click="load(1)" :class="{active:active===1}">Show first article</button>
        <button @click="load(2)" :class="{active:active===2}">Show second article</button>
        <p v-if="content.status === Status.pending">Loading...</p>
        <p v-else-if="content.status === Status.resolved">{{content.data}}</p>
        <p v-else-if="content.status === Status.rejected">Error while loading article</p>
    </div>
</template>
<script>
import PromiseStatus from 'reactive-promise-status';

export default {
    data(){
        return {content:new PromiseStatus(), active:null, Status:PromiseStatus.Status}
    },
    methods:{
        load(id){
            this.active=id;
            this.content.promise = fetch(`/article/${id}`).then(response=>response.json());
        }
    }
};
</script>
<style>
    button.active{
        color:red;
    }
</style>

Note, that I created one object of type PromiseStatus, and in load method I changed its property promise. That is because if you set content as null and try to check content.state you will get exception. That’s why there is 4th status noPromise