Autor Zpráva
gen001
Profil *
Ahoj,
procházel jsem novinky z es6 a narazil na generatory. jedna z hlavnich vyhod by mela byt ze narozdíl od abyčejné fce jsou neblokující.
tady je ukázkový kód:

setTimeout(function(){
    console.log("Hello World");
},1);

function foo() {
    // NOTE: don't ever do crazy long-running loops like this
    for (var i=0; i<=1E10; i++) {
        console.log(i);
    }
}

foo();
// 0..1E10
// "Hello World"

jak ale zařídit aby se hello word nevypsalo az na konci?
zkusil jsem neco jako nasledujici kod ale vysledek je bohuzel stejný (testováno v aktuální verzi chrome)

var start = new Date().getTime();
setTimeout(function(){
    var end = new Date().getTime();
    var time = end - start;
    console.log('Execution time: ' + time);
    console.log("Hello World");
},1);

function *foo(){
  var n = 1;
  for (var i=0; i<=1E4; i++) {
    console.log(n)
    yield n++;
  }
}

for (var p of foo()) {}

co dělám špatně? předem děkuji.
MartinP
Profil *
Neni for (...) {} blokující? Zkus místo toho jen zavolat tu fci.
Chamurappi
Profil
Reaguji na gena001:
co dělám špatně?
Už je to aspoň šest let, co jsem generátory v JS zkoumal, tak se možná pletu, ale podle mě vycházíš z mylných předpokladů. Generátor je jen jiný pohled na iterování umožňující hladší provázání kroků. Neblokují se navzájem provázané generátorové funkce (tzn. nečeká se na dokončení jedné, aby začala s polo-výsledkem pracovat druhá), což ovšem nemá vůbec žádný vliv na způsob zpracování událostí. Pořád máme jedno vlákno. Novinka v ES6 je jen syntaktický cukr (zpětně nekompatibilní!) pro něco, co šlo odjakživa napodobit.

Na vícevláknové úkony vznikly Web Workers.
gen001
Profil *
Chamurappi:
vycházel jsem z tohoto článku: http://davidwalsh.name/es6-generators
kde hned na začátku je příklad co jsem uvedl nahoře. možná tedy chybně jsem předpokládal že generátory mohou daný problém vyřešit.

přímo tam píší
With ES6 generators, we have a different kind of function, which may be paused in the middle, one or many times, and resumed later, allowing other code to run during these paused periods.


MartinP:
zkusil jsem to předělat na něco jako

function *foo(){
  var n = 1;
  while (true) {
    console.log(n)
    yield n++;
  }
}
 
var gen = foo()
gen.next()
gen.next()
10000x gen.next()

a vysledek je stejný. přijde mi, že po vykonání prvního gen.next() sice vznikne ona pauza na zavolání dalšího kódu ale první co se zavolá je další gen.next() a tak dále takže samotný callback z timeout se zavolá až po posledním gen.next().
Radek9
Profil
gen001:
přijde mi, že po vykonání prvního gen.next() sice vznikne ona pauza na zavolání dalšího kódu ale první co se zavolá je další gen.next()
To se děje proto, že přesně takhle jsi ten kód napsal. Jde o to, že můžeš zavolat next, potom tam nechat pauzu a později (tedy logicky asynchronně, nebo po nějaké sekvenci jiných příkazů) zavolat další next, přičemž generátor běží od toho místa, kde původně přestal. Můžeš to tedy kdykoli přerušit a potom obnovit ve chvíli, kterou ty uznáš za vhodné. To je ta výhoda.
Jan Tvrdík
Profil
gen001:
možná tedy chybně jsem předpokládal že generátory mohou daný problém vyřešit
Generátory ti umožní relativně snadno dělat to samé, co dělá každý procesor v PC – přepínat mezi jednotlivými úlohami, čímž vytvoříš iluzi paralelní běhu. Viz také výborný článek o yieldu v PHP.
gen001
Profil *
Radek9:
jistě, ale předpokládám že mezi zavoláním x-teho next() by měla být možnost, aby se vyhodnotil timeout který mezitím končí a vypsal se tedy někde uprostřed. nikoli až na konci.
Jan Tvrdík
Profil
gen001:
Ano, ale musíš si ručně napsat ten scheduler (česky asi plánovač procesů). Nefunguje to tak, že po zavolání yield budou automaticky zavolány úlohy, které čekají ve frontě (např. protože doběhl timeout).
Radek9
Profil
gen001:
předpokládám že mezi zavoláním x-teho next() by měla být možnost, aby se vyhodnotil timeout
Předpokládáš špatně. V tu chvíli ještě není uvolněn stack na hlavním vlákně, takže se ta událost z timeoutu řadí až za tu sekvenci volání next (nebo případně ještě za další kód, který následuje). Ty generátory opravdu nejsou žádné magické vylepšováky, blokují vlákno stejně jako cokoli jiného.
gen001
Profil *
ok, díky za info. pak vážně nechápu proč někdo v článku uvádí příklad na něčem co stejně nelze pomocí generátorů napsat lépe...
Jan Tvrdík
Profil
gen001:
Ale pomocí generátorů to jde napsat „lépe“, tedy že jedna operace nemusí čekat na dokončení druhé. Pochopil jsi aspoň něco z toho, co jsem výše psal?
gen001
Profil *
Jan Tvrdík:
ale samotný callback setTimeout bude stejně až nakonec. což mi připadalo jako hlavní bod k řešení. a ten zůstává nevyřešen...
Jan Tvrdík
Profil
gen001:
Ne nutně. Buď si můžeš napsat vlastní implementaci setTimeout, kdy ty callbacky bude volat scheduler nebo scheduler může uvolnit hlavní vlákno a přidat pomocí setTimeout s nulovým zpožděním pokračování sebe sama nebo scheduler může uvolnit hlavní vlákno a callback uvnitř setTimeout zavolá po svém dokončením explicitně scheduler aby pokračoval.

Nicméně všechno tohle jsou pro normální weby zbytečně komplikovaná řešení, nevím o tom, že by to tak někdo někdy používal, už proto, že generátory jsou podporované až v ES6, takže je musíš v praxi kompilovat do ES5.
Chamurappi
Profil
Reaguji na gena001:
a ten zůstává nevyřešen...
Nevyřešen na úrovni ECMAScriptu. Na něm ani není, aby tohle řešil, logicky to do něj nepasuje.
Metoda setTimeout je součástí DOMu, nastavuje časovanou událost, patří k API prohlížeče, nemůže změnit charakter jazyka. Od onclicku bys také neočekával, že může nastat uprostřed běhu jiné JS funkce.
_es
Profil
[#13] Jan Tvrdík:
Asi tomu nerozumiem. Čo myslíš po „uvoľnením hlavného vlákna“? Všetok JS beží v jednom vlákne a keď funkcia beží, tak nie je žiadny spôsob, ako ju z JS ukončiť - musí skončiť sama od seba, či ju ukončí niečo „nadradenejšie“ - napríklad zavretie prehliadača či niektoré prehliadače ponúknu ju zavrieť ako pridlho bežúci skript a pod. Nie je nič také ako možnosť spustiť funkciu na trebárs maximálne 1 s, potom spustiť inú funkciu, po jej dokončení prvú dokončiť a pod. Takže mi nie je jasné, ako by ten „scheduler“ mal fungovať.
Jan Tvrdík
Profil
_es:
Takže mi nie je jasné, ako by ten ‚scheduler‘ mal fungovať.
Četl jsi výše odkázaný článek nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html? Pokud si představíš ten scheduler jako jeden velký while(true) cyklus, tak uvnitř může být něco jako:

if (step % 100 == 0) {
    setTimeout(() => continueWith(state), 0); // zařadí nakonec fronty pokračování
    break; // uvolní hlavní vlákno
}

Taky je možný, že jsem něco nedomyslel =)
_es
Profil
Jan Tvrdík:
Článok sa predsa týka PHP, nie je tam nič o JavaScripte. Ak sa v JS spraví nejaký „velký while(true) cyklus“ tak to akurát zablokuje celú stránku. Použitie setTimeout s nulovým oneskorením namiesto priameho zavolania funkcie spôsobí len to, že sa funkcia zavolá až za všetkými dokončenými príkazmi za setTimeout. Žiadny „multitasking“ sa s tým nevyrobí.
Jan Tvrdík
Profil
_es:
Článok sa predsa týka PHP, nie je tam nič o JavaScripte.
Všechno co je v tom článku je IMHO aplikovatelné na JS. PHP má taky jedno vlákno. Syntaxe je skoro stejná.

Ak sa v JS spraví nejaký ‚velký while(true) cyklus‘ tak to akurát zablokuje celú stránku.
Nesmysl. Záleží přece na obsahu toho cyklu.

spôsobí len to, že sa funkcia zavolá až za všetkými dokončenými príkazmi za setTimeout.
Což bylo přesně cílem. Dočasně uvolní vlákno, aby mohl proběhnout hotový setTimeout a zase pokračuje.

Žiadny ‚multitasking‘ sa s tým nevyrobí.
Záleží na definici multitaskingu. Celou dobu tvrdím, že jde o stejnou emulaci, kterou dělá normální procesor. Multitasking v počítači nebo v telefonu taky funguje i na jednom jádře.
Radek9
Profil
Jan Tvrdík:
PHP má taky jedno vlákno. Syntaxe je skoro stejná.
Jenže v PHP ti je jedno, že to vlákno blokneš, protože ho na nic jiného nepotřebuješ.

Nesmysl. Záleží přece na obsahu toho cyklu.
Zkusils to? Jakýkoli uživatelský vstup je v tu chvíli znemožněn, ať ten while obsahuje cokoli.

Aby to fungovalo zhruba tak, jak ty říkáš, a přitom to nic neblokovalo, tak bys musel každou tu sekvenci příkazů řadit na konec stacku (použitím setTimeout nebo setImmediate). V případě toho generatoru nějak takhle:
setImmediate(function run() {
  if (!it.next().done) {
    setImmediate(run);
  }
});
Jan Tvrdík
Profil
Ach, já to asi nevysvětlím. Tak tady máte celé funkční řešení toho původního problému [#1] pomocí generátorů:

setTimeout(() => console.log("Hello World"), 20);


// jobs
function* foo() {
    for (var i = 0; i < 1000; i++) {
        console.log("A%d", i);
        yield;
    }
}

function* bar() {
    for (var i = 0; i < 700; i++) {
        console.log("B%d", i);
        yield;
    }
}


// scheduler
function run(i, jobs) {
    while (jobs.length) {
        var j = (i++) % jobs.length;
        if (jobs[j].next().done) {
            jobs.splice(j, 1);
        }

        if (i % 5 === 0) {
            setTimeout(() => run(i, jobs), 0);
            break;
        }
    }
}

run(0, [foo(), bar()]);
_es
Profil
Jan Tvrdík:
Záleží na definici multitaskingu.
Obvykle má ovládanie multitaskingu možnosť prerušiť beh pridlho trvajúcej úlohy a spustiť iné. To v JS nie je možné.
gen001
Profil *
Jan Tvrdík:
vazne diky moc za ukazku

Vaše odpověď

Mohlo by se hodit

Neumíte-li správně určit příčinu chyby, vkládejte odkazy na živé ukázky.
Užíváte-li nějakou cizí knihovnu, ukažte odpovídajícím, kde jste ji vzali.

Užitečné odkazy:

Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm: