• NOTE: Conversation title has changed: Read the first three posts - #1..#3 - under the subtitle bind() not working because it was triggered by trying to make require() and .connect() and all kinds of initializers to work correctly after save() and power recycle in standalone operation using a sequencer as presented in post #6.

    In some initialization sequencing I had the construct that simplified to the essentials looks like this:

    // .bind() not working?
    
    var obj = 
    { id:"obj"
    , cnt: 0
    , meth:function() {
        if ("obj" == this.id) {
          console.log("'this' is 'obj':"); 
          console.log("id="+ this.id + " in invocation # " + (++this.cnt));
          if (this.cnt < 3) { setTimeout(this.meth,1); }
        } else {
          console.log("'this' is (unexpectedly) NOT 'obj'"); 
        }
      }
    }; 
    
    obj.meth.bind(obj);
    
    function onInit() {
      obj.meth();
    }
    

    Above code uploaded to Espruino (PICO) and onInit() invoked in the console produces the following output:

              |_| http://espruino.com
     1v81 Copyright 2015 G.Williams
    >echo(0);
    =undefined
    >onInit();
    'this' is 'obj':
    id=obj in invocation # 1
    =undefined
    'this' is (unexpectedly) NOT 'obj'
    >
    =undefined
    

    First invocation obviously works - because it is a standard invocation - but second - in set Timeout(... in line 10 - fails.

    Afaiu fnctn.bind(ctx) binds the ctx object to the function fnctn so that this in function is resolved to ctx context object. As examples shows, Espruino js does not.

    Changing line 10 to

          if (this.cnt < 3) { var _this = this; setTimeout(_this.meth,1); }
    
    

    does not help.

    Changing onInit() {... to

    function onInit() {
      var f = obj.meth;
      f();
    }
    

    shows that .bind() is not working.

             |_| http://espruino.com
     1v81 Copyright 2015 G.Williams
    >echo(0);
    =undefined
    >onInit();
    'this' is (unexpectedly) NOT 'obj'
    =undefined
    >
    

    I could reason that it would not work with multiple objects created with a constructor and meth had been defined on the prototype, and then bound to one of the created objects, and if multiple times bound, the last would win (...but it is not or neither,... see next post...).

  • An interesting turn takes this code - which - in a nutshell:

    • defines the same structure but with use of constructor and prototype
    • creates first an obj with id = "objx"
    • creates second an object with id = "obj"
    • invokes .meth() on obj

    // .bind() not working?
    
     
    function Obj(id) {
      this.id = id;
      this.cnt = 0;
    }
    Obj.prototype.meth = function() {
        console.log("-----.meth()-----");
        console.log(this);
        console.log("id=" + this.id);
        if ("obj" == this.id) {
          console.log("'this' is 'obj':"); 
          console.log("id="+ this.id + " in invocation # " + (++this.cnt));
          if (this.cnt < 3) { var _t = this; setTimeout(_t.meth,1); }
        } else {
          console.log("'this' is (unexpectedly) NOT 'obj'"); 
        }
        console.log("-----/.meth()-----");
    }; 
    
    
    var objx = new Obj("objx");
    
    var obj = new Obj("obj");
    
    obj.meth.bind(obj);
    
    function onInit() {
      obj.meth();
    }
    

    The output for the bound this produced with console.log(this) must have somthing to do with the constructor, becauase all - both - instances objx and obj are listed:

             |_| http://espruino.com
     1v81 Copyright 2015 G.Williams
    >echo(0);
    =undefined
    >onInit();
    -----.meth()-----
    {
      "id": "obj",
      "cnt": 0 }
    id=obj
    'this' is 'obj':
    id=obj in invocation # 1
    -----/.meth()-----
    =undefined
    -----.meth()-----
    {
      "Obj": function (id) {
      this.id = id;
      this.cnt = 0;
    },
      "objx": {
        "id": "objx",
        "cnt": 0 },
      "obj": {
        "id": "obj",
        "cnt": 1 },
      "onInit": function () {
      obj.meth();
    },
      "console": function () { [native code] }
     }
    id=undefined
    'this' is (unexpectedly) NOT 'obj'
    -----/.meth()-----
    > 
    
  • Did you try this on desktop JavaScript? I just did with jsbin and it behaves exactly the same way in your first example.

    I think your problem is that you're not using bind correctly. bind returns a bound function, but doesn't alter the one you pass as an argument.

    So:

    a.bind(b); // does nothing
    a = a.bind(b); // does what you want
    
  • Ic - ...dddooooh.. :(). Therefore, the proper usage is (see line 15);

    // .bind() working this way:
    var obj = 
    { id:"obj"
    , cnt: 0
    , meth:function() {
        if ("obj" == this.id) {
          console.log("'this' is 'obj':"); 
          console.log("id="+ this.id + " in invocation # " + (++this.cnt));
          if (this.cnt < 3) { setTimeout(this.meth,1); } // <--- 'OK'
        } else {
          console.log("'this' is (unexpectedly) NOT 'obj'"); 
        }
      }
    }; 
    obj.meth = obj.meth.bind(obj); // <--- adjusted
    function onInit() {
      obj.meth();
    }
    

    And the output confirms that .bind() works when 'properly' used:

              |_| http://espruino.com
     1v81 Copyright 2015 G.Williams
    >echo(0);
    =undefined
    >onInit()
    'this' is 'obj':
    id=obj in invocation # 1
    =undefined
    'this' is 'obj':
    id=obj in invocation # 2
    'this' is 'obj':
    id=obj in invocation # 3
    > 
    

    Alternative - to avoid the awkward redefinition / reassignment - and for the case of multiple intances, it is better to adjust line 10 as follows and to drop line 16 altogether:

          if (this.cnt < 3) { setTimeout(this.meth.bind(this),1); } // <--- 'BETTER'
    

    Drawback of this cleaner solution is the recreation of the bound function everytime... To avoid this, the bound function could be assigned as separate .methBound property and reused this way:

    ...
          if (this.cnt < 3) { setTimeout(this.methBound,1); } // <--- 'OK'
    ...
    ...
    obj.methBound = obj.meth.bind(obj); // <--- bind once
    ...
    
  • I could not help but to read your post with s/obj/crystal/g and found it kind of funny. crystal.meth.bind(crystal); is the way to go...?

    Got it.

  • ...yep, the abstraction and reduction to the minimum made it look like this... Actual code is this code below and hardware as attached pic... and it works pretty neat... it is a way to get out of callback hell and reduce it to sequence hell... I do not know what is better... global controller gCtl s the 'drug' to connect all kinds of (different types of) 'crystals' with meth(ods)... in correct sequence and with set forth configuration(s)... LOL

    // /ESP8266/xyz/abc.js
    
    // g(lobal)Log(ging) function:
    var gLog = function(v) { console.log(v); };
    
    // g(lobal) C(on)t(ro)l(ler) that handles:
    // - sequencing of initializers
    // - run workers after save() and power-cycle / power-on reset
    var gCtl = 
    { is:[], i:-1 // (registered) i(initializer)s, index for iterating initializers 
    , adi: function(i) { this.is.push(i); } // add/register initializer
    , ini: function(ok,str) { if (this.i >= 0) { // invoke registered initializers
        gLog("i[" + this.i + "]" + ((str)?str:"") + ": " + ((ok)?"ok":"failed")); } 
        if (ok) { if (++this.i < this.is.length) { var _t = this; 
          setTimeout(function(){_t.is[_t.i](functi­on(ok,txt){_t.ini(ok,txt);});},1);}  
        } else { /* is[i]() failed */
          /* code to handle failures... may be retries... not built/used yet */
      } }
    , run: function() { this.i = -1; this.ini(true); } // run() invoked in onInit()
    };
    
    /* 
     * CONFIGS AND MODULES
     */
    
    // g(lobal)D(isplay) and its DSP_C(onfiguration) objects
    var gD = null, DSP_C = 
    { md:require("ILI9341")
    , spi:SPI1
    , spiC: {sck:A5, miso:A6, mosi:A7, baud:1000000}
    , dc:B10, cs:B1, rst:B13
    };
    
    // g(lobal) T(e)rm(inal) and its T_C(onfiguration) objs using gD(isplay) and 
    var gTrm = null, T_C = {w:240, h:320, lh:12};  gTrm = 
    { c:T_C 
    , cl: 0 
    , p: function(l,nl) { var c = this.c;
        if (this.cl + c.lh > c.h) { this.cl = 0; }
        gD.setColor(0,0,0); gD.fillRect(0,c.cl,c.w-1,c.l + (c.lh*2) - 3);
        gD.setColor(1,1,1); gD.drawString(l,0,this.cl);
        if (nl) { this.cl += c.lh; }
      } 
    };
    
    // http and its HTTP_C(onfiguration) objs - mainly list of urls 
    var http = null, HTTP_C =
    { md:require("http") 
    , urls:
      [ "http://www.pur3.co.uk/hello.txt"
      , "http://weather.noaa.gov/pub/data/foreca­sts/marine/coastal/pz/pzz535.txt"
      ]
    };
    
    // g(lobal) W(ifi) and its WIFI_C(onfiguration) objects
    var gW = null, WIFI_C =
    { md:require("ESP8266WiFi_0v25")
    , ser:Serial2, bd:115200, serC:{tx:A2, rx:A3}
    , ssid:"myWLAN", lak:"myWLANpassword" 
    , ip:"", p:80 
    };
    
    /* 
     * INITIALIZERS
     */
    
    // display initializer with callback 'cb' to return control to gCtl sequencer
    // does display setup and connect spi to it as set forth in configuration and
    // clears display
    gCtl.adi(function(cb){ var c = DSP_C;
      c.spi.setup(c.spiC);
      gD = c.md.connect(c.spi, c.dc, c.cs, c.rst, function(){ 
        gD.clear();
        cb(true,"display");
      }); }
    );
    
    // wifi initializer 1 (create wifi) with callback 'cb' to return control to gCtl sequencer
    // does display setup and connect serial to it as set forth in configuration and
    // creates g(lobal)W(ifi) object
    gCtl.adi(function(cb){ var c = WIFI_C;
      c.ser.setup(c.bd, c.serC);                       
      gW = c.md.connect(c.ser, function(err){                     
        if (err) { cb(false,"wifi.reset: " + err); }
        else { cb(true,"wifi.reset"); }
      }); }
    );
    // wifi initializer 1 with callback 'cb' to return control to gCtl sequencer
    // does display setup and connect via spi to it as set forth in configuration
    gCtl.adi(function(cb){ var c = WIFI_C;
      gW.connect(c.ssid, c.lak, function(err){                         
        if (err) { cb(false,"wifi.connect: " + err); }
        else { cb(true,"wifi.connect"); }
      }); }
    );
    
    /*
     * WORKERS
     */
    
    function getb(udx) { var c = HTTP_C;
      gLog(c.urls[udx] + ":");
      c.md.get(c.urls[udx], function(res){
        res.on('data', function(dta){
          // gLog(dta);
          gTrm.p(dta,true);
        });
      });
    }
    
    function getm(udx) { var c = HTTP_C;
      gLog("---> " + c.urls[udx] + ":");
      var d = true, l = "", ol, nl;
      c.md.get(c.urls[udx], function(res){
        res.on('data', function(dta){
          l = l + dta;
          var ls = l.split("\n"), lm = ls.length - 1, lx = -1;
          while (++lx < lm) { ol = (l = ls[lx]).length;
            if ((d = d && (l != "$$"))) {
              while (ol > (nl = (l = l.replace(" OR LESS","|<")).length)) ol = nl; 
              while (ol > (nl = (l = l.replace(" TO ","~")).length)) ol = nl; 
              while (ol > (nl = (l = l.replace(" TO","~")).length)) ol = nl; 
              gTrm.p(l,true);
            } }
          l = ls[lm]; ls = null; 
        });
      });
    }
    
    function onInit() { gCtl.run(); }
    

    gCtl is global Control for initializers... and the discussed line is line # 15, which - in this case uses nested anonymous functions... (Above code does include application main flow/control yet... it is next: for example to store the info/forecast and go to a deep sleep until next user action to show stored info or info/forecast expires - zoom in on pic below to read what is displayed... gTerm - terminal - is not working correct yet: override / scroll...). setTimeout() is used to break the invocation chaining / avoid nested calling / to do stack 'reset' (to give Espruino a memory relieve...)

    The global controller for initializations is used to separate configuration from initialization - first - and - second - to have reliable constructs to call from onInit() afer saving the code and re-powering the device disconnected from Web IDE. An earlier approach was the Tasker - Task synchronizer - kind of a 'purposed' Promise. Another serialization approach has been discussed/suggested in conversation [How to carry out functions in a sequential manner]. (http://forum.espruino.com/conversations/­276388).

    Latter allowed to put calls like commands into a fifo and execution has to await an ack before moving on to the next command... almost the same as making a callback.

    Log output of above initializer produced in line 13 looks like:

    i[0] display: ok
    i[1] wifi.reset: ok
    i[2] wifi.connect: failed
    

    In case of failed, above - very lean - initializer control has no retries or error handling (vs the not so lean Tasker has).


    1 Attachment

    • PICO_ESP8266-01_ILI9341_NOAA_Msgs.jpg
  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

sequencing - configure, require(), initializing w/ onInit() and save() ...and bind()

Posted by Avatar for allObjects @allObjects

Actions