Moi
Pitäisi ratkoa ongelma, jossa rajapinnalta pyydetään "rekursiivisesti" uutta dataa eli rajapinnalta kutsutaan niin kauan dataa, että saadaan rakennettua polku tiedostoon, joka on päämäärä.
Käytännössä rajapinta palauttaa hakemistorakenteen yhdeltä tasolta, jonka osia pitää kutsua, jotta saadan seuraavan tason tiedot ja rakennettua "hakemistopuu".
Ensin tehdään alustava kutsu rajapinnalle, joka palauttaa JSONia, jossa on listattu joko seuraavat kutsuosoitteet tai ladattavat tiedostot:
{id: "hakemisto1",id:"hakemisto2"...} tai {id:"my.file",id:"another.file"}
Tiedoissa on ainoastaan tiedostonimiä tai seuraavia polkuja, muttei koskaan molempia.
Skriptin pitää siis nuuskia, että onko kyseessä seuraava "URL-taso" vai tiedostolista ja toimia sen mukaan.
Jos kyseessä on url, niin sitten se looppaa sen läpi GETillä ja taas nuuskii palautuvaa jsonia.
Jos jsonissa on viittauksia tiedostoihin, niin se lataa sen ja tekee siitä hötömölöä.
Eli ensin api.fi/
ja sitten api.fi/hakemisto1/ tai api.fi/my.file (riipuen rajapinnan palauttamasta datasta)
Tämä "puu" voi jatkua ja haarautua äärettömästi api.fi/hakemisto1/hakemisto1.1/
api.fi/hakemisto1/hakemisto1.2/ jne.
Yritin jqueryn getillä jonkinlaista rekursiota, mutta osoite meni aina lopulta ihan sekaisin.
Sen lisäksi silmukat pyörivät hyvin omituisesti eli ensinnä aloitettu silmukka(isäntä) käydään läpi ennen kuin silmukan sisällä aloitettu silmukka.
Lisäys:
Nykyinen säätö:
function rekursio(uusiUrl){ $.get(rajapinta+juurihakemisto+'/'+uusiUrl.join('/'),function(data,status){ for (var hakemisto in data) { if (!regexPattern.test(data[hakemisto].id)) { // Tässä tarkistetaan regexillä, että onko kyseessä hakemisto- vai tiedostonimi uusiUrl.push(data[hakemisto].id); rekursio(uusiUrl); } else { // Jos regex palauttaa false, niin kutsutaan funkkaria, joka lataa tiedoston ja tekee siitä hötömölöä hotomoloa(rajapinta+juurihakemisto+'/'+uusiUrl.join('/')+'/'+data[hakemisto].id); } } }); }
Auttaisko jos lisäisit tuohon rivin 6 jälkeen uusiUrl.pop(); ?
Nythän silmukassa lisäät tuohon uusiurl -taulukkoon aina kaikki löytyvät hakemistot, mutta et poista niitä missään vaiheessa.
Aika vammainen rajapinta muuten, jos regexillä pitää tunnistaa onko tiedosto vai hakemisto
Grez kirjoitti:
Auttaisko jos lisäisit tuohon rivin 6 jälkeen uusiUrl.pop(); ?
Nythän silmukassa lisäät tuohon uusiurl -taulukkoon aina kaikki löytyvät hakemistot, mutta et poista niitä missään vaiheessa.
Taisin sitä kyllä kokeilla, mutta kokeilenpa vielä varmuuden välttämiseksi.
Grez kirjoitti:
Aika vammainen rajapinta muuten, jos regexillä pitää tunnistaa onko tiedosto vai hakemisto
:D niin, tämä tosiaan palauttaa vain sen yhden tason rakenteen eikä muuta.
Ja ohjelmisto, johon tämä rajapinta liittyy, nojaa pitkälti noihin hakemistorakenteisiin...
Lisäys:
Grez kirjoitti:
Auttaisko jos lisäisit tuohon rivin 6 jälkeen uusiUrl.pop(); ?
Eipä auttanut tämäkään. Mun mieletsä tähän pitäisi saada joku laskuri tms. jonka perusteella se sitten muokkaan tuota uusiUrl taulukkoa (ts. silmukkakohtaisesti).
Ja jotenkin tuntuu, että sen pitäisi tietää kulloinkin palautuneen jsonin koko ja toimia sen mukaan...evot.
Kutsujen perusteella se tekee ensin 1. silmukan ja vasta sen jälkeen ko. silmukan sisällä aloitetun silmukan, jolloin hakemistorakenteen osalta haut kohdistuu ihan väärin.
So. se ei ikinä pääsee noihin tiedostoihin asti.
...Muutenkin aivot niin maitohapoilla, ettei taho kyetä käsittämään tätä solmua vaikka melko triviaalilta kuullostaa...
Ai niin tietenkään noi kutsut ei oo synkronisia ja toi uusiURL on kaikille funktioille yhteinen muuttuja. (Ei mulla aivot kytkeneet että JS vaikka selvästi viestissä niin lukikin)
Yksinkertaisinta olis varmaan kloonata toi uuriURL ennen kutsua.
var polku = $.clone(uusiUrl) polku.push(data[hakemisto].id); rekursio(polku);
Olisiko helpompaa, jos uusiUrl
olisi merkkijono taulukon sijaan? Tarpeen tullen sen saa kuitenkin splitillä taulukoksi ihan yhtä helposti kuin joinilla toiseen suuntaan. Rekursiivinen kutsu voisi olla yksinkertaisesti
rekursio(uusiUrl + '/' + data[hakemisto].id);
En osaa, en ymmärrä. Mitenkään päin en saa tätä toimimaan.
Lopulta näköjään palauttaa jo 429 - too many requests (30 taitaa olla maksimi).
Mitenkähän tuotakin lasketaan... Jos hakemistopuu onkin todella iso, niin eikös tuo tule vastaan melko nopeasti? Vai laskeeko tuo yksittäisiä api-kutsuja eli 30 kutakin kutsua tms. 30 / minuutti ...
Lisäksi tuo rajapinta palauttaa virheellisen kutsun kohdalla tuon juurihakemiston jsonin ja antaa ymmärtää, että kaikki on ihan ok. Huoh.
jlaire kirjoitti:
(10.05.2016 14:43:39): Olisiko helpompaa, jos uusiUrl olisi merkkijono...
Näin se oli hyvin varhaisessa vaiheessa, mutta onko sillä toiminallisuuden kannalta eroa?
Lisäys:
Päivitän tähän nyt tämän hetkisen tuloksen:
Kun juurihakemiston tiedot haetaan, niin se palauttaa jsonin, jossa on kolme tulosta.
Tämän jälkeen se kutsuu APIa json[0] arvon mukaisesti, jonka jälkeen se lisää seuraaviin kutsuihin aina seuraavan eli:
api.fi/json[0]/ api.fi/json[0]/json[1]/ api.fi/json[0]/json[1]/json[2]/
eli ihan väärin.
Tämän jälkeen se siirtyy json[0] alta löytyneen jsonin kimppuun eli silmukoi sen.
Ja nämäkin kutsut menevät tietysti väärin, kun nyt se hakee ylläolevan jatkeena niitä vaikka sen olisi pitänyt tehdä se jo 1. silmukassa kutsuttavassa silmukassa, muttei tee.
Miksei? Miksi se "hyppää" sen silmukan yli ja suorittaakin sen eri kohdassa kuin missä sitä kutsutaan?
Lisäys:
Lisäämällä silmukoihin breakit, koodi tulostaa oikeaoppisesti, mutta vain 1. tuloksen:
function rekursio(uusiUrl){ $.get(rajapinta+juurihakemisto+'/'+uusiUrl.join('/'),function(data,status){ for (var hakemisto in data) { if (!regexPattern.test(data[hakemisto].id)) { // Tässä tarkistetaan regexillä, että onko kyseessä hakemisto- vai tiedostonimi uusiUrl.push(data[hakemisto].id); rekursio(uusiUrl); break; } else { // Jos regex palauttaa false, niin kutsutaan funkkaria, joka lataa tiedoston ja tekee siitä hötömölöä hotomoloa(rajapinta+juurihakemisto+'/'+uusiUrl.join('/')+'/'+data[hakemisto].id); break; } } }); }
Lisäys:
Poistin 2. breakin ja nyt se yrittää myös seuraava tiedostoa.
Jostain syystä se tekee sen rekursio funktion avulla vaikka sitä ei kutsuta missään vaiheessa.
Eli for silmukassa sen pitäisi tunnistaa tiedosto ja kutsuta sen kohdalla vain hotomoloa.
Nyt se hyppää toisen tiedoston kohdalla yhtäkkiä rekursio funktioon.
Ja käyttää tietysti tiedostopolkua uusiUrlina vaikka sitä ei aseteta eikä lisätä mihinkään muuttujaan, joka kulkee rekursio-funktiossa.
Tää olis pitäny hoitaa meidän koodareiden puolesta ja mielellään backendissä...
Ei helevata.
Nyt tuo regex ei muka täsmää toisen tiedoston kanssa vaikka ne ovat käytännössä samalla tavoin nimetty. En ymmärrä.
Lisäys:
001-filename.ext true 002-filename.ext false
regex = /ext$/igm;
Ota molemmat break:t pois, niissä ei ole mitään järkeä. Kun koodi ei toimi oikein, ei kannata kokeilla tehdä tällaisia satunnaisia muutoksia, koska ne vain vaikeuttavat ymmärtämistä.
Ennen rekursiivista kutsua sinun pitää lisätä joko Grez:n ehdottama kloonaus tai siirtyä käyttämään merkkijonoja.
Ota regexistä g-flägi pois niin toimii.
Moi
Kiitti! g:n poisto auttoi, mutta miksi?
Breakit lisäsin osittain siitä syystä, että koodin suoritus pysähtyy eikä tule niin hirveästi dataa kerralla.
En tunne tuota clonea ja ainakaan noin suoraviivaisesti sitä ei voinut käyttää tuossa yhteydessä.
a.cloneNode is not a function
Se liittyy tuohon javascriptin regex olion toteutukseen. Tuolla g-määrittimellä on tarkoitus ajaa samaa merkkijonoa monta kertaa, jolloin se säilöö mistä etsimistä jatketaan.
Nyt vaikka vaihdat merkkijonoa, niin se ei aloita etsimistä alusta.
Jos siis tuota regexpiä moneen kertaan niin mätsiä seuraavalla ajolla saat käytännössä aina false, ellei uusi merkkijono ole selkeästi pidempi kuin edellinen.
http://stackoverflow.com/questions/18462784/why-is-javascript-regex-matching-every-second-time
Multibyte kirjoitti:
En tunne tuota clonea ja ainakaan noin suoraviivaisesti sitä ei voinut käyttää tuossa yhteydessä.
Näköjään se ei tosiaan suoraan toimi taulukolle.. No suoraan perus javascriptillä vaan tälläinen epäintuitiivisen näköinen taulukon taikakloonaus:
var polku = uusiUrl.slice()
Noh, kun ei tämä kerran alkuperäisen suunnitelmaan mukaan toiminut, niin vaihdoin lähestymistapaa.
Purin tämän kahteen funktioon, joista 1. hoitaa sen jsonin noutamisen ja 2. ao. mukaisesti päättelee, että haetaanko uusi kierros ja kutsuu kutsujaansa vai pitäisikö ulostaa ruudulle kutsuen tulostus funktiota. Ja tämä toimii, mikä on ihan ihmeellistä.
function olenSilmukoija(url, silmukkaData){ for (var simosilmu in silmukkaData){ if (re.test(silmukkaData[simosilmu].id)){ hotomoloa(api+url+'/'+silmukkaData[simosilmu].id); } else if (!re.test(silmukkaData[simosilmu])) { rekursio(url+'/'+silmukkaData[simosilmu].id); } } }
Tuon silmukan siirtäminen rekursio
:n sisälle toimisi kyllä myös. Ongelma korjaantui sillä, että muutit parametrin taulukosta merkkijonoksi, ei kahteen funktioon pilkkomisella. Mutta hyvä että toimii.
Tuo "else if (...)" olisi muuten selkeämpi pelkkänä "else"nä.
jlaire kirjoitti:
(11.05.2016 18:18:28): Tuon silmukan siirtäminen rekursio:n sisälle...
Hmm, pitääpä varsin huomenna kokeilla tuota taulukko vs mjono teoriaa.
En nimittäin ymmärrä, että mikä ero sillä on...
Juu, tuo Else IF on tässä yhteydessä turhan "vaativa", kun poikkeuksia ei lähtökohtaisesti ole.
Ja on se muutenkin hieman väärä ja puutteellisesti toteutettu tässä yhteydessä.
Tästä tuli nyt niin perus insinöörityö kuin olla ja taitaa. Enkä edes ole insinööri.
Koodi siis toimii, mutta ei mitään hajua, että miten.
Konsolista näkee, että se ensin silmukoi koko 1. jsonin.
Sen jälkeen näiden alkioiden sisällöt ja niin edelleen.
Tarkoituksenani on kuitenkin ollut luoda koodi joka ottaisi 1. jsonin ja sen 1. alkion ja etenee sitä pitkin kunnes tulee aika vaihtaa seuraavaan.
Jos tämän jotenkin pseudona ilmaisi niin:
nyt menee näin: 1 2 3 1.1 2.1 3.1
kun pitäisi mennä näin: 1 1.1 2 2.1 3 3.1
Vastaukset rajapinnasta saattaa tulla eri järjestyksessä kuin kutsut joita sinne lähetät. Jos kutsut rajapintaa järjestyksessä kutsuilla A,B,C,D - saattaa vastaukset tulla takaisin järjestyksessä B,D,A,C. Tämä on riippuvainen siitä miten rajapinnan toteutus on tehty (esim. mikäli siellä on queue -tyyppinen käsittely kutsuille, palautuu vastauksetkin kutsujärjestyksessä).
ja koska $.get:in success suoritetaan luonnollisesti vasta kun vastaus palautuu, on mahdollista että successin suoritusjärjestys voi näyttää hämärältä ja erilaiselta kuin loopissa ajettava kutsujärjestys.
Toinen, mitä en nyt käsitä/saa toimimaan, on setTimeout()
Jostain syystä tuon getin yhteydessä setTimeout jätetään ikään kuin huomiotta ja kaikki kutsut lähetetään sillä samalla punaisella sekunnilla.
Eli ts. kutsuttaessa:
window.setTimeout(loadURL, 500, url); // Tällä haetaan siis Jsonista löydetty tiedosto
silti se tykittää nuo kaikki kutsut (tässä tapauksessa löydetyt tiedostot) kerralla.
...Jaa vai puuttuukos tästä jokin lenkki. Kaikki lataukset jonoutuvat skriptissä muutaman millisekunnin välein, johon nykyisellään lisätään tuo 500ms koskien niitä kaikkia.
Eli ainoa "aikaero", mikä latausten välille tulee, on se normaali skriptissä syntynyt ero.
Tuo 500ms lykkääkin niitä vain kokonaisuutena.
SetTimeout lykkää vain sitä funktiokutsua, minkä sille antaa parametrina. Mitäs muuta olisit odottanut? Jos haluat suorittaa joukon funktiokutsuja viivästetyin aikavälein, niin sinun täytyy lisätä setTimeout jokaiseen kutsuun erikseen. Muutos olisi aika helppoa tehdä, ellet olisi valinnut rekursiota toteutustavaksi.
The Alchemist kirjoitti:
(16.05.2016 13:22:04): SetTimeout lykkää vain sitä funktiokutsua...
Hmm, tässä on nyt kolme funktiota ja kolme setTimeout kutsua.
Ensin kutsutaan rekursiota, jossa päätellään seuraava steppi.
Sitä kutsutaan timeoutin avulla, joiden sisällä olevia kutsutaan niin ikään timeoutin avulla.
Eli lähtökohtaisesti seuraavaa rekursiota kutsutaan vasta timeoutin jälkeen, jossa taas asetetaan uusi rekursio tai toiminto timeoutin kanssa.
Noh, muutin sen eilen niin, että tiedostopoluista muodostetaankin oma taulukko, joka loopataan lopuksi. Eli rekursiota käytetään ainoastaan polkujen selvittämiseen.
setTimeoutia käytetään sitten latausfunktion kutsumiseen.
Ei tämäkään tosin toiminut niin kuin ajatus, mutta ehkä se tänään tai huomenna ratkeaa.
Toimiva ratkaisu menisi tähän tapaan:
function aloita() { var data = [] function silmukka() { // Otetaan taulukosta yhdet parametrit. var p = data.pop() // Tehdään niillä parametreilla jotain, mutta rekursion sijaan // laitetaankin seuraavan "funktiokutsun" parametrit taulukkoon. if (plaaplaa(p[0], p[1])) { data.push([p[0] + "/toiset", p[1] + "/parametrit"]) } // Jos dataa on yhä, suoritetaan tämä funktio kohta uudestaan. if (data.length > 0) { setTimeout(silmukka, 100) } } // Aloitetaan em. silmukka. data.push(["ensimmäiset", "parametrit"]) silmukka() }
Minusta olisi hyvä opetella tekemään vaikeista välivaiheista geneerisiä funktioita, joita voi sen jälkeen käyttää missä tahansa ilman saman koodin ja samojen vaikeiden yksityiskohtien toistamista. Koodistakin tulee siistimpää, kun geneerinen osa on jossain muualla poissa silmistä, ja voi keskittyä ymmärtämään kulloisenkin tehtävän välivaiheet.
Uusimmassa javascriptin / ecmascriptin standardissa on mukana Promise-luokka, jolla asynkronisen map-funktion tekeminen on melko simppeliä. Promiseille on kattavasti polyfillejä, joten niitä voi käyttää myös sellaisessa koodissa, joka on tarkoitettu toimimaan vanhempienkin selainten kanssa. Myöhemmin esitelty ES6-syntaksin käyttö vaatii taas aivan viimeisimmät versiot selaimista, joten se ei välttämättä maksa vaivaa.
"use strict"; function mapWaitES5(items, callback) { var result = new Array(items.length); var index = 0; return new Promise(function(resolve, reject) { function process() { if (items.length) { /* * Promise.resolve() sallii callback-funktion palauttaa suoraan * tuloksena käytettävän arvon Promise-instanssin sijaan. * (Katso alempi esimerkki funktion mapWaitES5-funktion käytöstä.) */ Promise.resolve(callback(items.shift(), index)) .then(function(mapped_value) { result[index++] = mapped_value; process(); }) .catch(reject); } else { resolve(result); } } process(); }); } // Esimerkki asynkronisen koodin ajamisesta yksi rivi listan arvo kerrallaan. mapWaitES5(["A", "B", "C", "D"], function(value, index) { return new Promise(function(resolve, reject) { console.log(index + " -> " + value); setTimeout(resolve, 1000, index + value); }); }).then(function(result) { console.log("Got result", result); }, function(error) { console.error("Got error", error); }); // Käsittelyfunktio voi palauttaa suoraan tuloksen, mikäli asynkronisuutta ei tarvita. mapWaitES5(["A", "B", "C", "D"], function(value, index) { return value + index; }).then(function(result) { console.log("Got result", result); }, function(error) { console.error("Got error", error); });
Alla vielä sama mapWait-funktio ES6-standardin sallimalla luettavammalla syntaksilla.
"use strict"; function mapWaitES6(items, callback) { var result = new Array(items.length); var index = 0; return new Promise((resolve, reject) => { let run = () => { if (items.length) { Promise.resolve(callback(items.shift(), index)).then(mapped => { result[index++] = mapped; run(); }).catch(reject); } }; run(); }); } mapWaitES6(["Q", "X", "Y", "Z"], (value, index) => { if (index == 2) { throw new Error("Failed!"); } console.log(index + " -> " + value); return value + index; }).then( result => console.log("RESULT", result), error => console.error("ERROR", error) );
Aihe on jo aika vanha, joten et voi enää vastata siihen.