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

kai zhu kaizhu256 at gmail.com
Sun Mar 17 06:35:13 UTC 2019


*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] https://jsfiddle.net/kaizhu256/t9ubdenf/ <https://jsfiddle.net/kaizhu256/t9ubdenf/>

```html
<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>
```



> On 12 Mar 2019, at 15:49, guest271314 <guest271314 at gmail.com> wrote:
> 
> ```class RequestManager {
>   __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();```
> 
> On Mon, Mar 11, 2019 at 8:59 AM john larson <johnlarsondev1 at gmail.com <mailto:johnlarsondev1 at gmail.com>> wrote:
> 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 <x-msg://9/#m_-2222847355274947397_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 <x-msg://9/#m_-2222847355274947397_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.
> 
>  
> 
> On Sun, Mar 10, 2019 at 10:30 PM guest271314 <guest271314 at gmail.com <mailto:guest271314 at gmail.com>> wrote:
> 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();```
> 
> On Sat, Mar 9, 2019 at 11:42 AM john larson <johnlarsondev1 at gmail.com <mailto:johnlarsondev1 at gmail.com>> wrote:
> 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 <http://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 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes <https://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 <http://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 Regards,
> 
> John
> 
> 
>  <https://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 <https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=webmail&utm_term=link> <x-msg://9/#m_-2222847355274947397_m_-8918587750585472385_m_-8001099771182452640_m_-1339185979708762546_DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2>_______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org <mailto:es-discuss at mozilla.org>
> https://mail.mozilla.org/listinfo/es-discuss <https://mail.mozilla.org/listinfo/es-discuss>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20190317/11ecd105/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Screenshot 2019-03-10 at 08.45.13.jpeg
Type: image/jpeg
Size: 19739 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20190317/11ecd105/attachment-0001.jpeg>


More information about the es-discuss mailing list