ugrás a tartalomhoz

Szinkron dinamikus script betöltés

Karvaly84 · 2011. Feb. 28. (H), 14.02
Helló guruk!

Egy weboldalra szeretnék JavaScript-ből futásidőben JavaScript fájlokat betölteni (nem AJAX-al) úgy, hogy az oldal, vagy éppen az aktuálisan futó JavaScript kód kiértékelése megálljon addig míg a script be nem töltődött, és az abban deklarált változók, függvények elérhetővé nem válnak!

Valami olyasmit szeretnék hogy:
  1. alert(typeof module);   // undefined  
  2. require('module');      // szinkron betöltés  
  3. alert(typeof module);   // object  
Aki tud nem ajax-os megoldást és segíit annak köszönöm!

Közben ki gondoltam, hogy a require függvényben egy while ciklus pöröghetne addig amíg a typeof module értéke undefined vagy le nem jár egy timeout. Csak ez eléggé kontár munka lenne szerintem.
 
1

mootools assets

ironwill · 2011. Feb. 28. (H), 15.16
Szász Tibor mutatta be a mootools-t a februári budapest.js-en és többek közt erről a feature-éről is ejtett pár szót.

http://weblabor.hu/blog/20110209/budapestjs-februar

A működéséről nem igen tudok mit mondani, de, ha mást nem, a google tud segíteni:
mootools assets
2

Na igen ám, csak a bonyolult

Karvaly84 · 2011. Feb. 28. (H), 16.06
Na igen ám, csak a bonyolult keretrendszerek használatát elkerülném ha lehet, valami olyasmi dolog érdekelne, hogy a JavaScript elemeknek be lehet e állítani valami olyan objektum tulajdonságot amitől szinkron töltődik be ha be szúrom a DOM fába pl. Néztem a W3C oldalán a HTMLScriptElement-et de ott nem véltem felfedezni ilyesmit, bár ez nem zárja ki, hogy valahogy ne lehetne kivitelezhető.
4

Injektált script src-t

inf · 2011. Feb. 28. (H), 17.03
Injektált script src-t dom-mal, szerintem az is szinkron, de mindjárt kipróbálom.
Hát az injektálás nem nyert, idő kell, amíg frissül a dom fa, és végrehajtódik a script.

Aszinkron módon könnyebben megoldható, és nem fagy be a böngésző a betöltés közben.
3

Betöltés

Poetro · 2011. Feb. 28. (H), 17.00
Van rengeteg aszinkron kódbetöltő függvénytár. Nem tudom neked miért kell szinkron. A szinkron betöltéssel rengeteg probléma van, főleg azért mivel addig a felhasználó semmit se tud kezdeni a böngészővel, vagy legalábbis azzal a füllel, amíg egy szinkron esemény történik. És ha ez több másodpercig elhúzódik, akkor valószínűleg inkább becsukja az egész alkalmazást a vérbe, és sose látogat vissza az oldalra.
egy while ciklus pöröghetne

Mivel JavaScript egy szálon fut, ezért amíg a while ciklusod pörög, addig semmi más nem történik. Azaz a kód nem inicializálódik stb. Ez nem megoldás.

Mutatok pár megoldást aszinkron kód betöltésre, és hogy azokat hogyan kell használni:

load.js:
  1. load('jquery.js')  
  2.   .then('jquery-ui.js''jquery-ui-theme.js')  
  3.   .then('myscript.js').thenRun(function () {  
  4.     alert("itt már be van töltve minden, lehet használni őket");  
  5.   });  
LABjs:
  1. $LAB.script("jquery.js").wait()  
  2.   .script("jquery-ui.js").script("jquery-ui-theme.js").wait()  
  3.   .script("myscript.js").wait(function(){  
  4.     alert("itt már be van töltve minden, lehet használni őket");  
  5.   });  
Head JS
  1. head.js("jquery.js"function () {  
  2.   head.js("jquery-ui.js""jquery-ui-theme.js"function () {  
  3.     head.js("myscript.js"function() {  
  4.      alert("itt már be van töltve minden, lehet használni őket");  
  5.     });  
  6.   });  
  7. });  
Természetesen léteznek még szkript betöltő könyvtárak a fentieken kívül, én ezeket használtam, nagy sikerrel.
5

load.js

Poetro · 2011. Feb. 28. (H), 18.38
Ha nem tetszik, hogy a load.js kicsit túl sok globális változót használ, használhatod az én fork-omat belőle. Ekkor csak minimálisan változik a használat:
  1. loadJS.load('jquery.js')    
  2.   .then('jquery-ui.js''jquery-ui-theme.js')    
  3.   .then('myscript.js').thenRun(function () {    
  4.     alert("itt már be van töltve minden, lehet használni őket");    
  5.   });  
6

-

Karvaly84 · 2011. Feb. 28. (H), 23.57
Köszönöm, hogy törődtök velem, de éppen az ilyen hosszú sorokat szeretném el kerülni, kb olyan megoldásról álmodom mint a PHP-ben a require_once() csak js-ben. Ma nézegettem a neten de nem találtam semmit ami ilyesmit valósítana meg.
7

Mi a probléma?

Poetro · 2011. Már. 1. (K), 00.39
Nem értem mi a problémád. A JavaScript szerencsére nem PHP. amit az imént felírtam, az kb 4 require_once parancs lett volna ami legalább ilyen hosszú.
Ezen kívül természetesen vannak más különbségek is. Például az oldal betöltődése után a szkriptek már mindenképpen aszinkron töltődnek be, akkor is ha a script elemeket te rakod be a kódba. Hozzá kell szokni, hogy a JavaScript így működik. És előbb utóbb örülni fogsz, hogy így működik, és párhuzamosan tud betölteni több fájlt is.
8

szinkron betöltés

T.G · 2011. Már. 2. (Sze), 14.07
Miért "mindenképpen" aszinkron töltődnének be? Ha nem adjuk meg, hogy aszinkron töltődjön be, akkor a script tagok lefutása várakozik, míg van előtte lévő be nem töltött elem.
  1. <?php  
  2. if (isset($_GET['t'])) {  
  3.     sleep(rand(1, 3));  
  4.     die('console.log("' . $_GET['t'] . '");');   
  5. }  
  6. ?>  
  7. <html><head></head><body>  
  8. <script type="text/javascript">  
  9. var head = document.getElementsByTagName('head')[0];  
  10. for (var i = 0; i < 10; i++) {  
  11.     var script = document.createElement('script');  
  12.     script.setAttribute('type''text/javascript');  
  13.     //script.setAttribute('async', 'true');  
  14.     script.setAttribute('src''?t=' + i );  
  15.     head.appendChild(script);  
  16. }  
  17. </script>  
  18. </body></html>  
9

Aszinkron

Poetro · 2011. Már. 2. (Sze), 15.36
  1. <?php  
  2. if (isset($_GET['t'])) {  
  3.   sleep(rand(1, 3));  
  4.   die('console.log("load", ' . json_encode($_GET['t']) . ');');  
  5. }  
  6. ?>  
  7. <!DOCTYPE HTML>  
  8. <html lang="en-US">  
  9. <head>  
  10.   <meta charset="UTF-8">  
  11.   <title></title>  
  12. </head>  
  13. <body>  
  14. <script type="text/javascript">  
  15. var head = document.getElementsByTagName('head')[0];  
  16. for (var i = 0; i < 5; i++) {  
  17.   var script = document.createElement('script');  
  18.   script.setAttribute('type''text/javascript');  
  19.   //script.setAttribute('async', 'true');  
  20.   script.setAttribute('src''?t=' + i );  
  21.   head.appendChild(script);  
  22.   console.log('loop', i);  
  23. }  
  24. </script>  
  25. </body>  
  26. </html>  
Kimenet:
loop 0
loop 1
loop 2
loop 3
loop 4
load 0
load 1
load 2
load 3
load 4
És a hozzá kapcsolódó vízesés:

Amint látható a szkriptek párhuzamosan töltődnek le, de egymás után futnak. De nem szinkron módon, ugyanis akkor minden loop sor után egy load sor lenne.
10

A szinkron... :)

T.G · 2011. Már. 2. (Sze), 16.48
Szia! Szerintem ez ennél sokkal egyszerűbb, a script tagok sorrendbe futnak le. Az a script, ami létrehozza a többi scriptet az a többi script előtt van, tehát természetes, hogy az előbb végigfut. Szerintem. :)
11

Újra

Karvaly84 · 2011. Ápr. 21. (Cs), 09.22
Helló Guruk!

Ma újra neki ültem és AXAJ-al próbáltam meg megoldani az esetet, de a IE valamit nem kamáz!!! Aszt dobja, hogy: A művelet nem hajtható végre a következő hiba miatt: c00c023f. Ez mit jelent? Egy burkoló objektumot hoztam létre az XHR objektumnak és azon keresztül intézem a kérést:
  1. /** 
  2.  * [object _XMLHttpRequest] 
  3.  * @constructor 
  4.  * @param {String} method 
  5.  * @param {String} url 
  6.  * @param {Boolean} async 
  7.  * @param {String} user 
  8.  * @param {String} password 
  9.  * @returns {_XMLHttpRequest} 
  10.  * @see http://www.w3.org/TR/XMLHttpRequest/ 
  11.  */  
  12. function _XMLHttpRequest() {  
  13.     var xhr = new XMLHttpRequest(), _this = this;  
  14. //    this._client = xhr;  
  15.     this.open = function open() {  
  16.         return xhr.open.apply(xhr, arguments);  
  17.     };  
  18.     this.setRequestHeader = function setRequestHeader() {  
  19.         return xhr.setRequestHeader.apply(xhr, arguments);  
  20.     };  
  21.     this.send = function send() {  
  22.         return xhr.send.apply(xhr, arguments);  
  23.     };  
  24.     this.abort = function abort() {  
  25.         return xhr.abort.apply(xhr, arguments);  
  26.     };  
  27.     this.getResponseHeader = function getResponseHeader() {  
  28.         return xhr.getResponseHeader.apply(xhr, arguments);  
  29.     };  
  30.     this.getAllResponseHeaders = function getAllResponseHeaders() {  
  31.         return xhr.getAllResponseHeaders.apply(xhr, arguments);  
  32.     };  
  33.     xhr.onreadystatechange = function onreadystatechange() {  
  34.         // Ennél a metódusnál van a hiba.  
  35.         // IE valamiért nem akar.  
  36.         if (this.readyState)   _this.readyState   = this.readyState;  
  37.         if (this.status)       _this.status       = this.status;  
  38.         if (this.statusText)   _this.statusText   = this.statusText;  
  39.         if (this.responseText) _this.responseText = this.responseText;  
  40.         if (this.responseXML && this.responseXML.childNodes.length) _this.responseXML  = this.responseXML;  
  41.         // Ha a fenti sorokat try catch blokkba zárom akkor a végeredmény   
  42.         // IE9-ben jó, 7-ben, 8-ban, nem lesz jó.  
  43.         // Ha a try catch blokkokat be iktatjuk akkor a burkolónk  
  44.         // ugye nem kapja meg az aktuális "readyState",  
  45.         // "status", stb tulajdonságokat. Lehet valamit tenni?  
  46.         try {  
  47.             _this.onreadystatechange.apply(_this, arguments);  
  48.         }  
  49.         catch (e) {  
  50.         }  
  51.     };  
  52.     if (arguments.length) this.open.apply(this, arguments);  
  53. }  
  54. _XMLHttpRequest.prototype = {  
  55.     onreadystatechange : null,  
  56.     readyState         : 0,  
  57.     status             : 0,  
  58.     statusText         : '',  
  59.     responseText       : '',  
  60.     responseXML        : null  
  61. };  
  62. var _Require = {  
  63.     success : {},  
  64.     eval : function eval(url) {  
  65.         url = url.replace(/^~/, location.protocol + '//' + location.hostname);  
  66.         var success = _Require.success;  
  67.         if (url in success) return;  
  68.         var xhr = new _XMLHttpRequest('GET', url, false);  
  69.         xhr.onreadystatechange = function() {  
  70.             if (this.readyState === 4 && this.status === 200) {  
  71.                 window.eval(this.responseText);  
  72.                 success[url] = this.responseText;  
  73.             }  
  74.         };  
  75.         xhr.send(null);  
  76.     }  
  77. };  
  78. var require = _Require.eval;  
  79. require('~/KarvalyJS/karvaly/karvaly.js');  
  80. require('http://localhost/KarvalyJS/karvaly/test.js');  
  81. alert('karvaly: ' + typeof karvaly + '\ntest: ' + typeof test);  
Köszönöm aki segít!
13

Valószínűleg az a gond, hogy

Hidvégi Gábor · 2011. Ápr. 21. (Cs), 12.08
Valószínűleg az a gond, hogy szinkron kérésnél régebbi IE-ben nem fut le az onreadystatechange.
12

Szerintem olyan problémára

Hidvégi Gábor · 2011. Ápr. 21. (Cs), 12.01
Szerintem olyan problémára akarsz megoldást találni, ami fel sem vetődne, ha kicsit jobban ismernéd a HTTP fájlátvitel működését. Ez röviden így néz ki:

A HTTP-ben a legdrágább művelet a fájlkérések kiszolgálása, ami az esetek többségében úgy történik, hogy (a Firebug alapján)
- elindítjuk a kérést a szerver felé a megfelelő HTTP fejlécekkel
- a szerver leellenőrzi a fejlécek alapján, hogy volt-e már nálunk a fájl
- ha nem volt, akkor 200-as HTTP kóddal visszaküldi a fájl tartalmát
- ha volt, akkor 304-es kódot küld, azaz a fájl nem változott

A Weblaboron nyomtam pár frissítést, a műveletek átlagos válaszideje:
- kérés indítása: 16ms
- a kérés feldolgozása a szerveren statikus fájlok esetén: 30ms
- amennyiben még nem volt lenn a tartalom, egy kilobájtot nagyjából egy ezredmásodperc alatt töltök le (bár igazából ez nagyobb fájl [150k] esetén igaz, kisebb fájlok jóval lassabban jönnek le).

Ebből a kérés nettó ideje 16+30 = 46 ms, tehát minden egyes plusz fájlkérés felér 46 kilobájtnyi adat letöltésével. Bizonyos böngészők csak akkor kérik le a következő betöltendő scriptet, amennyiben az előzővel már végeztek, ugyanis a feldolgozásuk nem párhuzamusan történik.

A fentiekből következik, hogy nem éri meg az egyes scripteket külön fájlba tenni, hanem jóval célszerűbb őket egy fájlba pakolni, mert jóval gyorsabb lesz a feldolgozásuk.

Emellett Apache alatt érdemes használni a mod_expires kiterjesztést, a nem változó fájlokat berakni egy külön könyvtárba, és például a következő sorokat betenni egy .htaccess-be vagy a konfigurációs fájlba:

ExpiresActive On
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/x-javascript "access plus 1 month"

Ez azt csinálja, hogy egy hónapig a böngésző még kérést sem indít, hanem mindenképp a gyorsítótárból olvas.

Egy példa még: egy 400k-s js fájl (ext js keretrendszer) feldolgozása a böngészőben letöltés után kb. 300ms egy 1600 MHz-es Athlonon.
14

Disable Cache

Karvaly84 · 2011. Ápr. 22. (P), 06.20
A gyorstárazást ki lőttem, hogy mindig 200-at adjon vissza, de persze majd fel fogok készülni arra is ha esetleg mást ad vissza, most csak a alapot szeretném létre hozni.

Ilyesmi méréseket én is végeztem régebben, tény hogy igazad van sokkal rontja a sebességet ez a ötlet amit ki gondoltam, de egy olyan rendszert szeretnék készíteni ami egyes script-ek függőségét ellenőrzi, és a nagyobb class-okat külön fájlban kezelni.

Valóban a onreadystataechange nem fut le szinkronban, ezt most olvastam én is, ez a újabb böngészőkön nem is baj, csak a régieken, mint a IE 7-8 mert ott arra lettem figyelmes hogy ugyan úgy meghívja, ami ugye a burkoló onreadystatechange függvényét hívja meg, és ezért add hibát mikor le akarsz kérdezni valamit a natív XHR-től a burkolón keresztül, ez persze csak IE-ben van így, ahogy érzékeltem, szóval átírtam:
  1. function HttpRequest() {  
  2.     this._xmlHttpRequest = new XMLHttpRequest();  
  3.     this._xmlHttpRequest._httpRequest = this;  
  4.     if (arguments.length)  
  5.         this.open.apply(this, arguments);  
  6. }  
  7. HttpRequest.prototype = {  
  8.     constructor : HttpRequest,  
  9.     open : function open() {  
  10.     // Itt beállítok a natív objektumnak egy _async tulajdonságot.  
  11.         this._xmlHttpRequest._async = (arguments[2] === false) ? false : true;  
  12.         this._xmlHttpRequest.open.apply(this._xmlHttpRequest, arguments);  
  13.     },  
  14.     setRequestHeader : function setRequestHeader() {  
  15.         this._xmlHttpRequest.setRequestHeader.apply(this._xmlHttpRequest,  
  16.                 arguments);  
  17.     },  
  18.     send : function send() {  
  19.     // Itt ellenőrzöm, hogy szinkron küldés lesz e vagy nem, ha igen akkor  
  20.     // a natív objektum onreadystatechange tulajdonságát null-ra állitjuk.  
  21.     //  - A HttpRequest.onreadystatechange - lentebb.  
  22.         this._xmlHttpRequest.onreadystatechange = (this._xmlHttpRequest._async) ? HttpRequest.onreadystatechange  
  23.                 : null;  
  24.         this._xmlHttpRequest.send.apply(this._xmlHttpRequest, arguments);  
  25.     },  
  26.     abort : function abort() {  
  27.         this._xmlHttpRequest.abort.apply(this._xmlHttpRequest, arguments);  
  28.     },  
  29.     getResponseHeader : function getResponseHeader() {  
  30.         return this._xmlHttpRequest.getResponseHeader.apply(  
  31.                 this._xmlHttpRequest, arguments);  
  32.     },  
  33.     getAllResponseHeaders : function getAllResponseHeaders() {  
  34.         return this._xmlHttpRequest.getAllResponseHeaders.apply(  
  35.                 this._xmlHttpRequest, arguments);  
  36.     },  
  37.     onreadystatechange : null,  
  38.     readyState : 0,  
  39.     status : 0,  
  40.     statusText : '',  
  41.     responseText : '',  
  42.     responseXML : null  
  43. };  
  44. HttpRequest.onreadystatechange = function onreadystatechange() {  
  45.     with (this) {  
  46.         if (readyState)  
  47.             _httpRequest.readyState = readyState;  
  48.         if (status)  
  49.             _httpRequest.status = status;  
  50.         if (statusText)  
  51.             _httpRequest.statusText = statusText;  
  52.         if (responseText)  
  53.             _httpRequest.responseText = responseText;  
  54.         try {  
  55.             _httpRequest.onreadystatechange.apply(_httpRequest, arguments);  
  56.         } catch (e) {  
  57.         }  
  58.     }  
  59. };  
Ezzel már el érem a kívánt működést, így szinkronnál nem állítom be a onreadystatechange-t hanem a send metódus után egyből kérem a responseText-et.
Itt akad egy újjab probléma:
  1. var Script = {  
  2.     success : {},  
  3.     require : function require(url) {  
  4.         url = url.replace(/^~/, location.protocol + '//' + location.hostname);  
  5.         var success = Script.success;  
  6.         if (url in success) return;  
  7.         var xhr = new HttpRequest('GET', url, false);  
  8.         xhr.send(null);  
  9.     // Itt ez eval-al rengeteg bajom lenne: lentebb leirnám.  
  10.         eval(xhr._xmlHttpRequest.responseText);  
  11.         alert('karvaly: ' + typeof karvaly);  
  12.     }  
  13. };  
  14. var require = Script.require;  
  15. require('~/KarvalyJS/karvaly/karvaly.js');  
  16. alert('window.karvaly: ' + typeof karvaly);  
1. ha azt írom eval(xhr._xmlHttpRequest.responseText):
- IE 9 - függvényen belül elérem a karvaly-t,
globális scope-ból meg nem,
- IE 8 - ugyan az.
- IE 7 - ugyan az.
- FF 3.6 - ugyan az.
2. ha azt írom window.eval(xhr._xmlHttpRequest.responseText):
- IE 9 - elérhető a globális névtérből is.
- IE 8 - nem érhető el csak függvényen belül.
- IE 7 - it is csak a függvényen belül.
- FF 3.6 - elérhető a globális névtérből is.
3. az eval.call(window, xhr._xmlHttpRequest.responseText) ugyan úgy működött mint a 2. forma.

Nos azt nem tudtam, hogy az eval ilyen nehéz eset. Meglehet azt oldani hogy ha egy függvényen belül egy ilyen kód eval-ozva:
  1. var karvaly = (function(window){  
  2.     karvaly = {  
  3.         hello : 'szevasz'  
  4.     }  
  5.     return karvaly;  
  6. })(this)  
ki értékelődik akkor az a globális névtérben történjen minden böngészőben?
15

A végéhez: marad az, hogy

Hidvégi Gábor · 2011. Ápr. 22. (P), 08.11
A végéhez: marad az, hogy előre létre kell hoznod globálisan a karvaly változót.

Egyébként azt hiszem, értem, hogy mit akarsz csinálni, de nem sok értelmét látom. A Javascript nem PHP, meg nem Java, ezért nem kéne ráerőltetni az azokban megszokott programozási stílust, csak azért, mert ez mostanában annyira divatos.
16

Hali!

Karvaly84 · 2011. Jún. 9. (Cs), 12.51
Gondolkodtam a dolgon, mert ma kellet, és ha valakinek esetleg ugyan erre van szüksége akkor az alábbi kód most már jól müködik:
  1. var script = new function() {  
  2.     var success = this.success = {};  
  3.     var evaluate = function(src) {  
  4.         window.eval(src);  
  5.     };  
  6.     var exec = this.exec = function(src) {  
  7.         if (window.execScript)  
  8.             window.execScript(src);  
  9.         else  
  10.             evaluate.call(window, src);  
  11.     };  
  12.     this.require = function(url, retry) {  
  13.         retry = retry ? true : false;  
  14.         url = url.replace(/^~/, location.protocol + '//' + location.hostname);  
  15.         if (url in success) {  
  16.             if (retry) {  
  17.                 exec(success[url]);  
  18.                 return;  
  19.             }  
  20.             return;  
  21.         }  
  22.         with (new XMLHttpRequest) {  
  23.             open('GET', url, false);  
  24.             send(null);  
  25.             exec(responseText);  
  26.             success[url] = responseText;  
  27.         }  
  28.     };  
  29. };