Proposal For A New Alternative Keyword To “this” For Classes

# john larson (6 years ago)

Summary of the problem:

“this” keyword in Javascript is context dependent. And this is one of the culprits of most subtle and latent errors in Javascript. Moreover, use of “this” cannot be avoided if we are using classes and trying to reference instance properties.

When “this” is used in callback functions or in functions given to forEach as argument, IDEs rightfully cannot raise any design-time errors, giving developers the false sense of security, but we get run-time errors because “this” is undefined.

There seem to be two work-arounds:

  1.  Using arrow functions
    
  2.  Using .bind(this) syntax
    

Just assuming we forgot to use an arrow function or a .bind(), the IDE will not be able to raise an error and we will encounter the error in run-time.

What I propose:

I am proposing a new keyword that will be the alternative of "this" and will always point to the instance of the class. The name of the new keyword can be chosen with consensus from the community such that it would minimize/eliminate collision in existing codebases.

Here is a sample js code:

class RequestManager{

constructor(){

    this.successMessage = "Xhr successful.";

}





makeRequest() {

    var oReq = new XMLHttpRequest();

    oReq.addEventListener("load", this.responseHandler);

    oReq.open("GET", "www.google.com");

    oReq.send();

}



responseHandler() {

    window.alert(this.successMessage);

}

}

var reqManager = new RequestManager();

reqManager.makeRequest();

This piece of code will alert “undefined” because “this” is undefined in the callback function in strict mode.

Now let’s assume a new keyword is used insetead of “this” that will always point to the class instance.

As per its implementation, as described on developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes:

“JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.”

So with the new keyword introduced, behind the scenes, previous class could be interpreted as a piece of code along the lines of:

var RequestManager = function () {

var self = this;

self.successMessage = "Xhr successful";



self.makeRequest = function () {

    var oReq = new XMLHttpRequest();

    oReq.addEventListener("load", responseHandler);

    oReq.open("GET", "www.google.com");

    oReq.send();

};



var responseHandler = function () {

    window.alert(self.successMessage);

};

};

var reqManager = new RequestManager();

reqManager.makeRequest();

I believe this way, we would not have to resort to work-arounds for such a fundamental construct of the language and this would ease developers’ lives as someone forgetting to have used an arrow function or the .bind(this) syntax will not be a problem anymore.

Best ,

John

www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=icon

Virus-free. www.avast.com, www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=link <#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>

# Bergi (6 years ago)

I don't think we do need another keyword for this. People would forget to use that new keyword instead of using this, just like they currently forget to use arrow functions. That said, your desired "behind-the-scenes implementation" can already be achieved easily with the class fields proposal and an arrow function. However, there are many problems with that approach anyway.

kind , Bergi

# john larson (6 years ago)

Thanks for your input. I believe that it would be a trivial task for current static code analyzers to restrict usage of "this" for anyone opting in to use this new keyword exclusively. But the same does not hold true for other work-arounds such as arrow functions. And in addition to that, as you mentioned, arrow functions might have their own problems. Wouldn't such an alternative keyword be a good addition to our toolkit anyway?

Best , John

www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=icon

Virus-free. www.avast.com, www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=link <#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>

# Bergi (6 years ago)

I believe that it would be a trivial task for current static code analyzers to restrict usage of "this" for anyone opting in to use this new keyword exclusively.

Static tooling, like the TypeScript compiler, can detect problematic method usage already today. Sure, having a dedicated syntax for this will make static analysis simpler, but I don't deem that a worthy addition to the language.

As you mentioned, arrow functions might have their own problems. Wouldn't such an alternative keyword be a good addition to our toolkit anyway?

What I was trying to say is that your proposed alternative has exactly the same problems as instance-member arrow functions have today.

Best , Bergi

# Jordan Harband (6 years ago)

An additional keyword like this would require a function to have a hidden reference back to the instance. However, especially for class methods, but also for ES5-style inheritance, or even for class Foo {} Foo.prototype.bar = function () {}, methods are shared. You might have a billion instances, but only one function that uses your new keyword - how would the engine know which instance you were referring to?

# john larson (6 years ago)

Although the method lives on the prototype, the engine should already have knowledge of the object whose method is being invoked. I am not an expert on the internal workings of the engine, so I would be glad if anyone would correct me on this if I am wrong.

www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=icon

Virus-free. www.avast.com, www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=link <#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>

# Jordan Harband (6 years ago)

The engine only has that knowledge when you call it in a.b() form - at which point, this is already the instance. For a keyword to not be context dependent, it'd have to be the instance even when you'd done something like const { b } = a; b(). To do this would require either a) a has its own distinct copy of b that's bound to a, or b) a.b to be a getter that does that binding upon request.

Constructor-binding, field-binding, or arrow function fields all work the same way - they create a separate copy of a function for each and every instance, so that the function can point back to the instance even when extracted off of the object.

I'm not clear on how there'd be any other way to do it.

# Michael Luder-Rosefield (6 years ago)

How about binding this to a variable in the constructor? The following syntax throws an error due to this being a reserved word, so won't break any existing code:

class Foo {
  constructor (this: self, /* normal args */) {
    // ...
  }
}

I can see arguments against that as it breaks the convention for how parameter lists work, and also requires a constructor. Perhaps, then, we could either use something like a modified version of class field declarations to make class variables:

class Foo {
  const self = this;
  // ...
}

Or, maybe, allow parentheses before the class body to define variables:

class Foo (this: self) {
  //...
}

No, I don't like that one either.

The second suggestion (class variables as extension of class fields proposal) seems like it has potential to me, so it seems like an excellent time for everyone here to tell me why it's awful and stupid.

# Jordan Harband (6 years ago)

There'd still have to be a way for an arbitrary function - which (unless called in a.b() form) has no guaranteed connection back to a - to have a reference back to the object.

# Yulia Startsev (6 years ago)

I was asked by John to chime in, and I will repeat here what I sent to him directly via email.

Thanks for making the proposal and thinking through this problem. Indeed, this is a confusing concept for many people, and is an existing baggage from early JavaScript Design.

This having been said, it isn't clear to me what you are proposing. Your proposal is already possible in JavaScript as is, it is not adding a new capability to the language, it is instead introducing syntax as a work around that has already been extensively tooled. So, knowing this, how do we approach this problem? People are definitely struggling with this issue, so how can we do it better?

Let's play with this problem a bit. From your proposal it looks like you want a way to clearly identify methods on a class to have access to the this that is the class instance. At the moment, as you mention, you have to bind it in the constructor. What if it was a list of "bound methods"? or a keyword at the beginning of the class declaration called bound.

class Xyz {
  boundMethods = ["counter"];
   constructor() {
    this.count = 0;
    // something
  }

  counter() {
    this.count++;
  }

  render() {
  // some code
  element.addEventListener("click", this.counter);
  }
}

Well, that looks a bit ugly, and I can imagine a class having a lot of these and it not being much better than what we had before. Plus, it isn't clear to a programmer what is going on here. Let's try something else

What if we had a keyword like async?


class Xyz {
  constructor() {
    this.count = 0;
    // something
  }

  bound counter() {
    this.count++;
  }

  render() {
  // some code
  element.addEventListener("click", this.counter);
  }
}

You get the idea. So this is another way to think about the problem. But if we do this, then suddenly, we are going to be adding a huge list of keywords to the beginning! imaging bound async nameOfFunction(), and also how do you know what order they should be in?. So maybe there is another solution? Maybe someone has tried something before.

If we go through the current proposals that are in flight at the TC39, there is a proposal that also tries to tackle this, the decorators proposal. Take a look at how decorators solves the same problem: tc39/proposal-decorators#decorators

here is how it is done there (simplified, only showing @bound at work):


class Counter extends HTMLElement {
  _x = 0;

  @bound
  _clicked() {
    this._x++;
  }

  constructor() {
    super();
    this.onClick = this._clicked; // this is a private method, see the
proposal for more details
  }

  connectedCallback() { this.render(); }

  render() {

    // ... it does something, lets imagine creates an element that
listens for a click

    element.addEventListener("click", this.onClick)

  }
}

I removed some of the complexity of the original example, to help make it clear what is going on here in relation to what you were suggesting. The way it works is @bound makes clicked into an auto-bound method, replacing the explicit bind call later. You can read more about this in the proposal I linked. This is one way to address the issue that you bring up, without introducing a new keyword self.

It might be worth looking through some previous proposals to get a sense of how others have tried to tackle the problem. For the decorators proposal, it is really just a starting point -- you might find other points of reference. See if it is what you were looking for. If it doesn't fully address the case, it might point you in a direction that gets closer to a proposal, or it might bring up other interesting questions. We don't have to start with a fully formed solution -- collecting problems and constraints is an interesting way to work.

Hopefully this helps.

# Michał Wadas (6 years ago)

This is solvable with decorators or getters.

class Foo {

@cached get method() { return () => this.foobar(); }

}

class Bar {

@bound method() { return this.foobar(); }

}

Definitions for decorators @bound and @cached are pretty trivial.

# guest271314 (6 years ago)

This is probably not the pattern that is being proposed though outputs the expected result

class RequestManager {
  constructor() {
    this.successMessage = "Xhr successful.";
    RequestManager.THIS = this;
  }

  makeRequest() {
    var oReq = new XMLHttpRequest();
    oReq.addEventListener("load", this.responseHandler);
    oReq.open("GET", "");
    oReq.send();
  }

  responseHandler(e) {
    console.log(e, this); // `e`: event, `this`: XMLHttpRequest instance
    console.log(RequestManager.THIS.successMessage);
  }

}

var reqManager = new RequestManager();

reqManager.makeRequest();
# john larson (6 years ago)

First of all, thank you all for taking the time to review the proposal and sharing your valuable opinions. I would like to note that the proposal aims not to add a new capability that was not possible to do before but rather move the standard forward on the way for a modern, better and easier to use language for all the developers. Advancements of the language, or any language in that matter, throughout the last decade also followed a similar path because we were already able to do everything in one way or the other. We actually strive for the same thing, a better Javascript.

That being said, let me try to clarify my proposal further by walking you through my thought process:

Normally if I try to write a class similar to the sample code I have given in my first email using an object oriented programming language like Java, C# etc., I would be writing something similar to the following:

class RequestManager

{

    string successMessage = "Xhr successful.";



    void makeRequest()

    {

        var oReq = new XMLHttpRequest();

        oReq.addEventListener("load", responseHandler);

        oReq.open("GET", "*www.google.com* <#inbox/_blank>");

        oReq.send();

    }



    void responseHandler()

    {

        window.alert(successMessage);

    }

}

As you can see, I do not even have to use a special keyword for referring to methods from inside the class. Because they are already in lexical scope. Now, if this can be accomplished in Javascript without hitting some limitation/restriction due to the current state of the language, I think it would be the ideal solution. (This limitation might be the class syntax being just a syntactical sugar or some other reason that I cannot foresee right now and that would require a breaking change.) And I would happily change the proposal that way: “A no-keyword alternative for the “this””. If I should summarize this approach, I can say that every method of the class is going to assume the behavior we now have with arrow functions, but without requiring the use of the “this” and the arrow function syntax.

As contrary to the ideal solution, the last thing I would want would be to use a context-dependant keyword like the “this” to refer to methods/properties of the object and then try to set the context right by using binding or arrow functions. This referral should be lexical, not context-dependant. If I have the intent of referring to the instance method/property, that intent should manifest itself right there where I am using this method/property. I shouldn’t be looking at if this takes place inside an arrow function, or if the enclosing method is called with a binding or not. Why should I care about the enclosing of the call, right?

By the way, MDN also mentions the following about the use of arrow functions: “Arrow function expressions are ill suited as methods”.

@Yulia: Thanks for pointing out the decorator approach. But that also seems to deal with the enclosing and tries to solve the problem with a “context binding” approach. The only difference is the way it determines the binding. I am against this binding approach all together. Only the lexical scope of the code should be taken into consideration.

So far, I have laid out what I think the ideal solution is and what I think the problematic state we are in right now. And as a middle-ground, in case the ideal solution cannot be applied, I proposed a new keyword to use instead of the “this” so that it will always refer to the instance, regardless of execution context binding. In which case, when you replace the “this” in the problematic sample code in my initial email, it will work just fine. Let’ assume for the sake of this example that the new keyword is “self”:

class RequestManager{

constructor(){

    *self*.successMessage = "Xhr successful.";

}





makeRequest() {

    var oReq = new XMLHttpRequest();

    oReq.addEventListener("load", *self*.responseHandler);

    oReq.open("GET", "*www.google.com* <#inbox/_blank>");

    oReq.send();

}



responseHandler() {

    window.alert(*self*.successMessage);

}

}

var reqManager = new RequestManager();

reqManager.makeRequest();

As you can see, self.responseHandler will always point to the responseHandler method no matter whether the enclosing is a method, an arrow function or if it is called using a bind syntax or not.

I would be happy to further address your concerns about this explanation if you have any.

# Rob Ede (6 years ago)

I would imagine that this can be achieved with bind operator proposal, which already has Babel support, despite no examples showing usage inside a class.

Something like: oReq.addEventListener("load", ::this.responseHandler); seems to be the syntax that will de-sugar to oReq.addEventListener("load", this.responseHandler.bind(this)); to get you the desired this binding.

I’m surprised this idea hasn't been mentioned yet although things have been moving slowly on that proposal and it seems to need some community support to move along.

, Rob

References: 1: Bind Syntax Proposal (tc39/proposal-bind-operator, tc39/proposal-bind-operator)

2: Babel Plugin (babeljs.io/docs/en/next/babel-plugin-proposal-function-bind, babeljs.io/docs/en/next/babel-plugin-proposal-function-bind)

# john larson (6 years ago)

@Rob: Thanks for pointing out "proposal-bind-operator". I examined the proposal and as far as I understand, it is just another way to create a bound enclosing function. What I am proposing is just the opposite, no binding should take place in the enclosing function. A method call or usage of a property should already have its lexical scope pointing to the class instance and should not need any binding of its enclosing environment to function correctly.

# Ben Wiley (6 years ago)

The main issue here is that you're sort of asking for something too late.

If you reference the "notThis" keyword inside a callback method that has been separated from "its own" class instance, you're now saying "could you please do this all in the context of your instance", but your method doesn't know what its instance is because it never saved a reference.

Alternatively the compiler would notice that the "notThis" keyword is invoked inside a method, and automatically make a bound property on construction. The problem is that you're eagerly making a copy of every method using "notThis" for every instance that exists - eating up a whole bunch of extra memory for large collections - without knowing necessarily that those bound copies will be used. Granted, we're triggering the same problem when folks are using "class property arrow methods" for all their methods in React classes.

Ben

Le lun. 11 mars 2019 11 h 04, john larson <johnlarsondev1 at gmail.com> a écrit :

# john larson (6 years ago)

Well, actually this is the same discussion we had with *@Jordan Harband. *I think the js run-time already has that information at hand, so as long as we don't implement this as pure syntactical sugar, there would not be a need to keep an extra reference to anything, because it would be already there. The run-time will know which instance the invoked method belongs to. But as I said, it would be insightful to get a js engine expert opinion on this. Having said that, I believe it is just an implementation detail and can be handled one way or the other. I guess the “class property arrow methods in React" example you just provided also supports this notion.

# Ben Wiley (6 years ago)

Whatever the implementation, I'd be surprised to learn that the browser does have the information automatically and wouldn't need to store additional memory similar to .bind().

But I'm also not a browser engine expert, so there's that.

Le lun. 11 mars 2019 11 h 55, john larson <johnlarsondev1 at gmail.com> a écrit :

# Bergi (6 years ago)

I think the js run-time already has that information at hand, so as long as we don't implement this as pure syntactical sugar, there would not be a need to keep an extra reference to anything, because it would be already there. The run-time will know which instance the invoked method belongs to.

Well no, you're wrong here: the runtime does not have this information at hand. In your example (simplified)

var reqManager = new RequestManager();
function addEventListener(f) {
     console.log(f);
     f(event);
}
addEventListener(reqManager.responseHandler);

the addEventListener function will not know that the function f you passed was a method of the reqManager instance. It cannot distinguish that call from

addEventListener(RequestManager.prototype.responseHandler);

or

var g = otherReqManager.responseHandler;
addEventListener(g);

It is exactly the same function that is passed in all three cases. There is no instance bound to f, and f(event) will not invoke it as a method (with a receiver/this value).

Best , Bergi

# Isiah Meadows (6 years ago)

I've done a little engine work, and inline caches work by inline type maps based on the callee site. This can be used to reconstruct values + receivers, but only when the value is constant. It is not sufficient to ensure identity remains the same, and engines would still need a weak map to link methods to instances (as opposed to prototypes).

It's worth noting not even Java or Ruby offers this - their method references/objects (like our bound functions) are not memoized - they're linked to classes, not instances. Python is the exception here in auto-binding instance methods, not the norm.

# john larson (6 years ago)

So in terms of implementation, may be having instance method/property references on the objects and having static method/property references on the prototype is the solution?

# Ranando King (6 years ago)

I get what you're after. I touched on the same things when creating my private members proposal. The best of approaches for what you want is indeed relying on the lexical scope to act as a binding for all class members. There's just a couple of problems with doing things that way:

  1. ES is a dynamic language. This makes lexical binding difficult because it's entirely possible to call a non-static class method where this is undefined or null. Not allowing for that scenario will break existing code. Allowing for that scenario will cause variables to be unexpectedly either written to the global scope or throw.
  2. Class isn't really a "thing" in ES, at least, it isn't until class-fields lands. The use of the class keyword is currently completely optional. There's nothing in current ES that you can do with class that can't be done without it except use the super() call in the constructor function. But even that can be substituted with Reflect.construct(). Class-fields will destroy this symmetry, making class it's own unique "thing". But until then, what do you do about all the "classes" that don't use the class keyword?

Long and short, this means both the lexical scope approach and the alternate keyword approach will either break existing code or bring dubious-to-no benefit.

# Isiah Meadows (6 years ago)

@John May I suggest you play around a little with Lua? That language is prototype-based like JS, with several similar idioms, but it's a lot simpler, without new, property descriptors, or the like. Like JS, it also has a keyword self equivalent to JS this and inheritance is purely prototype-based. Unlike JS, it has no sugar for small functions nor does it have sugar for class-like constructs - it's like ES5 in that respect. Once you understand how prototypes work and how they're fundamentally different from classes, it will help you understand why method references, in the way you're describing them, don't make sense in that model.

# Isiah Meadows (6 years ago)

@Ranando Minor nit: class fields can be purely implemented in terms of defineProperty (for public) and weak maps (for private - what's used today for private data). Private methods could be implemented in terms of weak sets/maps and an object with methods outside the class's scope. Private static properties could just verify this === Type.

So no, those don't quite reify classes, either. (If something can be fully transpiled or polyfilled, it doesn't create or reify any new primitives.)

# Ranando King (6 years ago)

@Isiah Remember, the class-fields proposal has absorbed the private-fields proposal, and it is those private fields that have no precise equivalent. WeakMap and closures can approximate what private fields do. However, private fields has semantics and capabilities that cannot be fully reproduced in current ES. For instance, I can wrap both WeakMap and Proxy in a way that Babel private-fields will be proxy safe. With native private fields, that's impossible.

# guest271314 (6 years ago)
class RequestManager {
  // verbose `.bind()`
  __THIS__(method) {
    return new Proxy(method, {
      apply: (target, thisArg, argumentsList) => {
        return method.apply(thisArg, [...argumentsList, {
          THIS: this
        }])
      }
    })
  }
  constructor() {
    this.successMessage = "Xhr successful.";
  }
  makeRequest() {
    var oReq = new XMLHttpRequest();
    oReq.addEventListener("load", this.__THIS__(this.responseHandler));
    oReq.open("GET", "data:,");
    oReq.send();
  }

  responseHandler(e, {THIS} = {}) {
    console.log(this, e);
    window.alert(THIS.successMessage)
  }
}

var reqManager = new RequestManager();

reqManager.makeRequest();

As a keyword

oReq.addEventListener("load", __THIS__ this.responseHandler); // `THIS` within `this.responseHandler` will always refer to `this` instance

within

this.responseHandler `THIS` will always refer to `this` `class` instance

And/or

__THIS__ responseHandler(e, {THIS} = {}) {
  console.log(this, e);
  if (THIS) // `THIS` will always refer to `this` `class` instance
    window.alert(THIS.successMessage)
}
# Isiah Meadows (6 years ago)

Assuming Babel can get ahold of unmodified builtins, even if it's only during @babel/runtime initialization, it could still break that wrapper. core-js itself uses this trick extensively to dodge otherwise observable side effects in all of its wrappers. 😉

(Babel might be invoking wm.get(this) and wm.set(this), but it should really be binding those on initialization where possible.)

# kai zhu (6 years ago)

rant warning

-1 because classes generally have little-value in UX-workflow programming.

the example class RequestManager (given in this discussion), realistically has little reusability-value -- its no better than employing a throwaway static-function (both are equally likely to get rewritten each time UX-workflow features are added.).

for example, a common feature-request is adding visual-progress-bar. there is no "simple" way to extend RequestManager to do this, other than significant-refactoring of the base-class (and risk breaking class-dependencies downstream).

some other common UX feature-requests that would likely invalidate "reusability" of your class-based design include:

  1. needing to upload a binary-file (social-images, receipt-signatures, screenshots, etc...)
  2. needing to upload multiple binary-files in parallel (keeping track of timeouts of each)
  3. needing to upload multiple binary-files in parallel (keeping track of timeouts of each), and then download their thumbnail-previews from server to visually confirm uploads were correct
  4. needing to make parallel http-requests from 3rd-party sources and "joining" the response-data
  5. needing the sign-up-page to additionally pre-validate email / username / mobile-number / credit-card / etc... before form-submission to server

many frontend-engineers with experience "extending" products with additional UX-workflow features, know its rarely as simple as modifying some class-methods and be done with it -- it oftentimes require rewriting nearly every-piece-of-code that touches the given workflow needing enhancement.

p.s. -- here's a "simple" fully-working UX-example [1] on how to add visual-progress-bar to http-requests. if i had to additionally add some of the other UX-features mentioned above, it would likely entail me completely rewriting the throwaway static-function, rather than waste time trying to extend it.

[1] jsfiddle.net/kaizhu256/t9ubdenf, jsfiddle.net/kaizhu256/t9ubdenf

<style>
/* jslint utility2:true */
/* csslint ignore:start */
*,
*:after,
*:before {
    box-sizing: border-box;
}
/* csslint ignore:end */
body {
    background: #eee;
    font-family: Arial, Helvetica, sans-serif;
    font-size: small;
}
input {
    width: 100%;
}
textarea {
    font-family: Consolas, Menlo, monospace;
    font-size: smaller;
    overflow: auto;
    width: 100%;
}
</style>

<div id="ajaxProgressDiv1" style="background: #d00; height: 5px; left: 0; margin: 0; padding: 0; position: fixed; top: 0; transition: background 500ms, width 1500ms; width: 0%; z-index: 1;"></div>

<label>ajax-request</label><br>
<textarea id="input1" style="height: 10rem;">{
    "method": "GET",
    "url": "https://api.github.com/orgs/octokit/repos <https://api.github.com/orgs/octokit/repos>",
    "headers": {
        "accept": "application/vnd.github.v3+json"
    },
    "data": "hello world!"
}</textarea><br><br>

<button id="submit1">submit ajax-request</button><br><br>

<label>ajax-response</label><br>
<textarea id="output1" style="height: 20rem;"></textarea><br><br>

<script>
/*jslint browser*/
(function () {
    "use strict";
    var local;
    local = {};
    window.local = local;

    local.ajax = function (opt, onError) {
    /*
     * simple, throwaway ajax-function that can be easily rewritten
     * to accomodate new [async] ux-features
     */
        var resHandler;
        var xhr;
        opt.headers = opt.headers || {};
        opt.method = opt.method || "GET";
        xhr = new XMLHttpRequest();
        // open url
        xhr.open(opt.method, opt.url);
        // set req-headers
        Object.entries(opt.headers).forEach(function (entry) {
            xhr.setRequestHeader(entry[0], entry[1]);
        });
        // send data
        xhr.send(opt.data);
        // init request-handling
        resHandler = function (evt) {
        /*
         * this function will handle ajax-response
         */
            switch (evt.type) {
            case "abort":
            case "error":
                // decrement ajaxProgressCounter
                local.ajaxProgressCounter = Math.max(
                    local.ajaxProgressCounter - 1,
                    0
                );
                onError(new Error(evt.type), xhr);
                break;
            case "load":
                // decrement ajaxProgressCounter
                local.ajaxProgressCounter = Math.max(
                    local.ajaxProgressCounter - 1,
                    0
                );
                onError(null, xhr);
                break;
            }
            // increment ajax-progress-bar
            local.ajaxProgressUpdate();
        };
        // increment ajaxProgressCounter
        local.ajaxProgressCounter = local.ajaxProgressCounter || 0;
        local.ajaxProgressCounter += 1;
        // increment ajax-progress-bar
        local.ajaxProgressUpdate();
        xhr.addEventListener("abort", resHandler);
        xhr.addEventListener("error", resHandler);
        xhr.addEventListener("load", resHandler);
        xhr.addEventListener("loadstart", resHandler);
        xhr.addEventListener("progress", resHandler);
    };

    local.ajaxProgressUpdate = function () {
    /*
     * this function will update ajax-progress-bar
     */
        var ajaxProgressDiv1;
        // init state
        local.ajaxProgressCounter = local.ajaxProgressCounter || 0;
        local.ajaxProgressState = local.ajaxProgressState || 0;
        ajaxProgressDiv1 = document.querySelector(
            "#ajaxProgressDiv1"
        );
        // init ajaxProgressDiv1StyleBackground
        local.ajaxProgressDiv1StyleBackground = (
            local.ajaxProgressDiv1StyleBackground
            || ajaxProgressDiv1.style.background
        );
        // show ajaxProgress
        ajaxProgressDiv1.style.background = (
            local.ajaxProgressDiv1StyleBackground
        );
        // increment ajaxProgress
        if (local.ajaxProgressCounter > 0) {
            local.timerIntervalAjaxProgressHide = (
                local.timerIntervalAjaxProgressHide
                || setInterval(local.ajaxProgressUpdate, 2000)
            );
            // this algorithm will indefinitely increment ajaxProgressBar
            // with successively smaller increments without ever reaching 100%
            if ((ajaxProgressDiv1.style.width.slice(0, -1) | 0) > 95) {
                ajaxProgressDiv1.style.width = "0%";
                local.ajaxProgressState = 0;
            }
            local.ajaxProgressState += 1;
            ajaxProgressDiv1.style.width = Math.max(
                100 - 75 * Math.exp(-0.125 * local.ajaxProgressState),
                ajaxProgressDiv1.style.width.slice(0, -1) | 0
            ) + "%";
        } else {
            // finish ajaxProgress
            ajaxProgressDiv1.style.width = "100%";
        }
        // cleanup timerTimeout
        clearTimeout(local.timerTimeoutAjaxProgressHide);
        // hide ajaxProgress
        local.timerTimeoutAjaxProgressHide = setTimeout(function () {
            ajaxProgressDiv1.style.background = "transparent";
            local.ajaxProgressCounter = 0;
            local.ajaxProgressState = 0;
            // reset ajaxProgress
            clearInterval(local.timerIntervalAjaxProgressHide);
            local.timerIntervalAjaxProgressHide = null;
            setTimeout(function () {
                if (!local.ajaxProgressState) {
                    ajaxProgressDiv1.style.width = "0%";
                }
            }, 500);
        }, (
            local.ajaxProgressCounter > 0
            ? local.timeoutDefault
            : 1000
        ));
    };

    // init event-handling
    document.querySelector(
        "#submit1"
    ).addEventListener("click", function () {
        var output1;
        output1 = document.querySelector(
            "#output1"
        );
        // reset #output1
        output1.innerHTML = "";
        try {
            local.ajax(JSON.parse(document.querySelector(
                "#input1"
            ).textContent), function (err, xhr) {
                // handler err
                if (err) {
                    output1.textContent = err.stack;
                    return;
                }
                // output response-headers
                output1.textContent = (
                    "status-code:\n"
                    + xhr.status
                    + "\n\nresponse-headers:\n"
                    + xhr.getAllResponseHeaders()
                    + "\n\nresponse-text:\n"
                    + xhr.responseText
                );
            });
        // handler errCaught
        } catch (errCaught) {
            output1.textContent = errCaught.stack;
        }
    });
}());
</script>
# Isiah Meadows (6 years ago)

UX workflows aren't all of JS. Classes exist for many more reasons than that, and 99% of my classes are for abstracting non-trivial business logic and ensuring those are easily testable, not directly UI/UX.


Isiah Meadows contact at isiahmeadows.com, www.isiahmeadows.com

# Michael Luder-Rosefield (6 years ago)

I'm getting deja vu again