[JS/jQuery] onhashchange event triggert vóór binding

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • geez
  • Registratie: Juni 2002
  • Laatst online: 23-09 17:13
Ik probeer een JS hashchange event te unbinden, om zo de hash te kunnen veranderen zonder dat de relevante event handler gecalled wordt.

Echter als ik dat doe, vuurt de relevante handler alsnog. Als ik er een alert() tussen plaats, gaat het wél goed. Ik heb ook gepoogd een (lange) for loop ertussen te plaatsen, zelfde probleem.

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
// Remove event handler
jQuery(window).off('hashchange');

// Set browser URI to data
window.location.hash = watnuttigedata;

// Deze alert is nodig, anders triggert de functie alsnog..
alert('test');

// Enable hashchange event again
jQuery(window).on('hashchange', function(){
    handle_hash();
});


Ik heb nog even gezocht naar een soort ready() functie in jQuery, zonder succes (i.t.t. sommige jQuery functies heeft off() geen ready functie als laatste argument). Elders op internet suggereerde iemand om binnen de on() functie de handler in zijn eigen function(){} te handlen (anders vuurt hij direct), maar dat heb ik al.

Ideeën?

[ Voor 4% gewijzigd door geez op 07-11-2016 18:49 ]

Beste antwoord (via geez op 01-11-2016 21:19)


  • kaesve
  • Registratie: Maart 2009
  • Laatst online: 16-05 03:04
Ik gok dat als je het in een timeout zet, het gefixed zal zijn. Dus zo:

JavaScript:
1
2
3
4
5
window.timeout(function() {
    jQuery(window).on('hashchange', function(){
        handle_hash();
    });
}, 0);

Alle reacties


Acties:
  • Beste antwoord
  • 0 Henk 'm!

  • kaesve
  • Registratie: Maart 2009
  • Laatst online: 16-05 03:04
Ik gok dat als je het in een timeout zet, het gefixed zal zijn. Dus zo:

JavaScript:
1
2
3
4
5
window.timeout(function() {
    jQuery(window).on('hashchange', function(){
        handle_hash();
    });
}, 0);

Acties:
  • 0 Henk 'm!

  • ThomasG
  • Registratie: Juni 2006
  • Laatst online: 23-09 14:00
kaesve schreef op maandag 31 oktober 2016 @ 21:22:
Ik gok dat als je het in een timeout zet, het gefixed zal zijn. Dus zo:

JavaScript:
1
2
3
4
5
window.timeout(function() {
    jQuery(window).on('hashchange', function(){
        handle_hash();
    });
}, 0);
In jQuery los je dat op met jQuery.ready, wat je kunt verkorten tot jQuery(handler) als functie:
JavaScript:
1
2
3
4
5
jQuery(function() {
 jQuery(window).on('hashchange', function() {
  handle_hash();
 });
});


Tenzij je conflicten hebt, kun je ook gewoon $ gebruiken inplaats jQuery

Acties:
  • 0 Henk 'm!

  • geez
  • Registratie: Juni 2002
  • Laatst online: 23-09 17:13
@kaesve: Neem aan dat je `setTimeout` bedoelde, en dat werkt inderdaad! Wat vreemd, wat is de reden daarvan?

@ThomasG: `jQuery.ready()` lijkt hier niet te werken omdat die alleen vuurt wanneer de DOM klaar is met laden, wat in dit geval al gebeurd is.

Acties:
  • 0 Henk 'm!

  • kaesve
  • Registratie: Maart 2009
  • Laatst online: 16-05 03:04
ThomasG schreef op maandag 31 oktober 2016 @ 21:28:
[...]
In jQuery los je dat op met jQuery.ready, ...
Ah, ik wist niet dat je dat op die manier kon gebruiken (ik ben niet echt fan van jQuery). Helemaal hetzelfde is dat volgens mij ook niet. window.setTimeout zou je ook kunnen gebruiken voordat het DOMContentLoaded event afgevuurd wordt.
geez schreef op maandag 31 oktober 2016 @ 22:13:
@kaesve: ... Wat vreemd, wat is de reden daarvan?
...
Oeps, ja, die bedoelde ik.. Uitleggen vindt ik eerlijk gezegd een beetje lastig. Het komt omdat setTimeout de callback 'asynchroon' aanroept; de code die je na setTimeout schrijft wordt direct uitgevoerd, niet pas wanneer setTimeout helemaal klaar is. Omdat je javascript single threaded uitgevoerd wordt, betekent dat, dat die setTimeout op zijn vroegst de callback aan kan roepen als de huidige functie aanroep afgelopen is.

[ Voor 40% gewijzigd door kaesve op 31-10-2016 22:26 ]


Acties:
  • 0 Henk 'm!

  • BlueZero
  • Registratie: Mei 2007
  • Laatst online: 10-09 15:45
Het kan ook nog zonder timeout mits dat gewenst is door een tweede variabele toe te voegen, zie onderstaand voorbeeld.

JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Set extra variable to prevent handle_hash
_triggerHandleHash = false;
window.location.hash = watnuttigedata;

// Enable hashchange event again
jQuery(window).on('hashchange', function(){

if(_triggerHandleHash === true){    
handle_hash();
}

_triggerHandleHash = true;

});

Acties:
  • 0 Henk 'm!

  • geez
  • Registratie: Juni 2002
  • Laatst online: 23-09 17:13
kaesve schreef op maandag 31 oktober 2016 @ 22:14:
[...]

Oeps, ja, die bedoelde ik.. Uitleggen vindt ik eerlijk gezegd een beetje lastig. Het komt omdat setTimeout de callback 'asynchroon' aanroept; de code die je na setTimeout schrijft wordt direct uitgevoerd, niet pas wanneer setTimeout helemaal klaar is. Omdat je javascript single threaded uitgevoerd wordt, betekent dat, dat die setTimeout op zijn vroegst de callback aan kan roepen als de huidige functie aanroep afgelopen is.
Ja, maar juist omdát JS singlethreaded wordt uitgevoerd zou het setten van de window.location.hash voltooid moeten zijn voor de callback eraan gehangen wordt? Lijkt alsof er dan toch iets asynchroon loopt.
BlueZero schreef op maandag 31 oktober 2016 @ 23:06:
Het kan ook nog zonder timeout mits dat gewenst is door een tweede variabele toe te voegen, zie onderstaand voorbeeld.
[...]
Ik heb dit nog niet getest maar dit zou ervoor zorgen dat de eerste trigger van het onhashchange event simpelweg onderdrukt wordt, omdat hij sowieso lijkt te vuren door deze snippet. Dan snap ik nog steeds niet waaróm dat gebeurt :|

[ Voor 24% gewijzigd door geez op 01-11-2016 08:13 ]


Acties:
  • 0 Henk 'm!

  • kaesve
  • Registratie: Maart 2009
  • Laatst online: 16-05 03:04
geez schreef op dinsdag 01 november 2016 @ 08:09:
[...]

Ja, maar juist omdát JS singlethreaded wordt uitgevoerd zou het setten van de window.location.hash voltooid moeten zijn voor de callback eraan gehangen wordt? Lijkt alsof er dan toch iets asynchroon loopt.
Alle events worden asynchroon afgehandeld in JS. De browser/het platform bepaalt wanneer welke events worden afgevuurd. Een voorbeeld dat in mij op komt zijn touch events, die in bepaalde situaties tientallen milliseconden later wordt afgevuurd dan dat de touch geregistreerd wordt door het device. Omdat een nieuwe eventhandler toch pas aangeroepen kan worden als er geen javascript code meer wordt uitgevoerd, kan ik me voorstellen dat de browser wacht/events verzamelt totdat de huidige call stack afgelopen is. Dan kan de browser kijken welk event de hoogste prioriteit heeft, om die dan af te vuren.

Als proefje kun je bijvoorbeeld dit doen:

JavaScript:
1
2
3
4
5
6
7
8
9
window.location.hash = "newValue";

for (var i = 0; i < 10000; i++) {
  console.log(i);
}

window.addEventListener("hashchange", function() {
  console.log("The new hash is", window.location.hash);`
}


Die for-loop duurt bij wel even een paar seconden. De hash wijzigt meteen, maar die event handler vuurt ook na die paar seconden.

Acties:
  • 0 Henk 'm!

  • BlueZero
  • Registratie: Mei 2007
  • Laatst online: 10-09 15:45
Binding en unbinding van events is asynchroon in javascript, eigenlijk kun je in het kort stellen dat de event handling en Ajax calls asynchroon worden uitgevoerd omdat de browser niet kan vaststellen wanneer ze gebeuren.

In jouw voorbeeld is er een event handler actief op hashchange, die unbind je, alleen je wacht niet tot dit voltooid is, javascript heeft inmiddels de variabele al geset en de oude handler wordt uitgevoerd.

Asynchrone functies hebben in javascript daarom vaak een callback die pas wordt uitgevoerd op het moment dat de functie is voltooid. Die gebruik je in de on functie wel maar niet in de off functie.

Oftewel wat je ook kan doen is het volgende:

JavaScript:
1
2
3
4
5
6
7
8
9
10
11
jQuery(window).off('hashchange', function(){
    
    //set hash to new value
    window.location.hash = watnuttigedata;
    
    // Enable hashchange event again
    jQuery(window).on('hashchange', function(){
        handle_hash();
    });

});

Acties:
  • 0 Henk 'm!

  • kaesve
  • Registratie: Maart 2009
  • Laatst online: 16-05 03:04
BlueZero schreef op dinsdag 01 november 2016 @ 20:18:
Binding en unbinding van events is asynchroon in javascript, ...
Is dat zo? Ik dacht dat het binden/unbinden synchroon gebeurt, maar dat de events asynchroon worden afgevuurd. Als wat jij zegt klopt, zou ik verwachten dat in dit voorbeeld:

JavaScript:
1
2
3
4
5
6
7
8
9
10
    function logEvent() {
        console.log("hash", window.location.hash);
    }

    window.addEventListener("hashchange", logEvent);

    window.setTimeout(function () {
        window.removeEventListener("hashchange", logEvent);
        window.location.hash = "hello";
    }, 0);


de eventhandler nog wordt aangeroepen, omdat removeEvent asynchroon uitgevoerd wordt. Dit lijkt niet zo te zijn?

Acties:
  • 0 Henk 'm!

  • geez
  • Registratie: Juni 2002
  • Laatst online: 23-09 17:13
Dank voor de reacties heren :)

@kaesve: Een soortgelijke test heb ik inderdaad ook gedaan (een for loop met wat berekeningen die in totaal een paar seconden duurde). In dat geval vuurde het event inderdaad na afloop. De alert() lijkt ervoor te zorgen dat het event wel vuurt in de tussentijd maar omdat er geen handler aan hangt in dat geval gebeurt er niets.

@BlueZero: Wat je nu zegt klopt niet helemaal volgens mij; als ik de unbinding doe en direct erna de hashchange (zoals in mijn voorbeeldcode), zonder vervolgens het event opnieuw te binden, vuurt het event niet. Echter, staat de rebind er wel dan moet de alert tussen de hashchange en het opnieuw binden van het event staan (of met de setTimeout()) om te voorkomen dat het event vuurt.

De callback bij voltooiing van jQuery's off() zocht ik ook naar inderdaad, maar die blijkt niet te bestaan: https://api.jquery.com/off/

[ Voor 8% gewijzigd door geez op 01-11-2016 21:30 ]


Acties:
  • +1 Henk 'm!

  • kaesve
  • Registratie: Maart 2009
  • Laatst online: 16-05 03:04
Als je de details wil weten, dan is het keyword 'event loop'. Mdn heeft bijvoorbeeld een artikel: https://developer.mozilla.../Web/JavaScript/EventLoop

Acties:
  • 0 Henk 'm!

  • BlueZero
  • Registratie: Mei 2007
  • Laatst online: 10-09 15:45
Excuus, ik had het inderdaad verkeerd er is geen callback voor de off, dus of een setTimeout of mijn vorige voorbeeld dan maar.

Niet alle events zijn synchroon in javascript zie hiervoor https://w3c.github.io/uievents/#sync-async

En nu dan een iets langere uitleg over waarom de hash change triggered ook al is er een unbind, het volgende voorbeeld triggered ook een hashchange:

JavaScript:
1
2
3
4
5
window.location.hash = 'test';

$(window).on('hashchange', function() {
    alert('why is this triggered?');
});


Nu zou je ook zeggen maar die on handler was er toch nog niet toen de hash werd geset. Dit klopt, echter dit heeft te maken met hoe de hashchange event werkt.

Zie ook de specificatie hiervoor: https://www.w3.org/TR/html5/browsers.html#navigate-fragid

- De browser bekijkt asynchroon vanaf de nieuwe entry tot aan de huidige url de history
- Als de hash is veranderd trigger het event hashchange

Oftewel het bekijken of een hash daadwerkelijk is gewijzigd gebeurt asynchroon, op dat moment wordt eerst de synchrone code in de huidige loop uitgevoerd door javascript voordat de asynchrone code wordt voltooid. En het setten van een listener is wel synchroon.

Waarom voorkom je het dan met een setTimeout, setTimeout wordt in de wachtrij gezet na het asynchrone event hashchange en wordt dus inclusief functies in de callback van setTimeout pas later uitgevoerd.

Acties:
  • 0 Henk 'm!

  • BlueZero
  • Registratie: Mei 2007
  • Laatst online: 10-09 15:45
kaesve schreef op dinsdag 01 november 2016 @ 21:25:
[...]

Is dat zo? Ik dacht dat het binden/unbinden synchroon gebeurt, maar dat de events asynchroon worden afgevuurd.
Nee dit is niet zo, na een dag code schrijven ga ik me vergissen, het zijn inderdaad de events niet de bindings, alleen als het event asynchroon is (wat niet elk event is) wordt de binding ook indirect asynchroon.

Events bestaan namelijk sowieso wel, een click handler, touch, hashchange wordt al uitgevoerd door de browser dit gebeurd in dit geval dus asynchroon. Met een bind subscribe je op dit event, die subscribe wordt synchroon aangemaakt alleen de trigger ervan kan afhankelijk van het event asynchroon binnen komen.

Zie hiervoor mijn bovenstaande uitleg.

Acties:
  • 0 Henk 'm!

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
BlueZero schreef op dinsdag 01 november 2016 @ 23:23:
Waarom voorkom je het dan met een setTimeout, setTimeout wordt in de wachtrij gezet na het asynchrone event hashchange en wordt dus inclusief functies in de callback van setTimeout pas later uitgevoerd.
Alleen de grote vraag is : Is dit spec of is dit proefondervindelijk vastgesteld?

Ik kan me ooit eens voorstellen dat er een engine maker gaat komen die multi-threads gaat ondersteunen en dan gaan dit soort dingetjes keihard falen.

Sowieso is het een beetje afhankelijk van geluk of dit gaat werken of niet. Ik zie in ieder geval geen reden (behalve random) waarom settimeout na de asynchrone calls geplaatst moet worden, waarom niet ervoor?

Acties:
  • +1 Henk 'm!

  • kaesve
  • Registratie: Maart 2009
  • Laatst online: 16-05 03:04
Gomez12 schreef op dinsdag 01 november 2016 @ 23:51:
[...]

Alleen de grote vraag is : Is dit spec of is dit proefondervindelijk vastgesteld?

Ik kan me ooit eens voorstellen dat er een engine maker gaat komen die multi-threads gaat ondersteunen en dan gaan dit soort dingetjes keihard falen.

Sowieso is het een beetje afhankelijk van geluk of dit gaat werken of niet. Ik zie in ieder geval geen reden (behalve random) waarom settimeout na de asynchrone calls geplaatst moet worden, waarom niet ervoor?
Hoe de event loop werkt is duidelijk vastgelegd hoor. Whatwg heeft een spec, en het w3c ook. Die beschrijven duidelijk wat er in welke volgorde moet gebeuren.

De code van de TS met setTimeout zal altijd hetzelfde resultaat opleveren (al heb je geen garantie dat die timeout direct na 0ms wordt uitgevoerd. Als de browser geen processortijd heeft, kan het langer duren). De browser heeft (zoals dus in die spec beschreven) een task queue, waar in het code voorbeeld van dit topic dus een hashchange-event task op wordt gezet gevolgd door een task voor de timeout. Wanneer de huidige call frame afgelopen is, zal de browser die twee tasks oppakken en uitvoeren. Nu zijn er geloof ik speciale cases met events/tasks die voorrang kunnen krijgen, maar dit is niet een non-deterministisch process waar je maar geluk moet hebben.

[ Voor 8% gewijzigd door kaesve op 02-11-2016 01:32 ]

Pagina: 1