Node.js piekło callbacków ...

0

Witam dopadły mnie te piekielne callbacki i niestety po dwu dniach poległem . Prosta pętla ona działa ale co z tego jak nie moge swierdzić kiedy ona się skończy!!!. Próbowałem callbacków w pętlach, promise w funkcji którą otoczyłem pętle i biblioteki wait.for-es6. I nie dałem rady, wymyśliłem brzydki ale działający sposób odświeżanych setTimeoutów. Potrzebuję callbacka który po zakończeniu pętli posortuje tablicę i zapisze je do pliku.

days.forEach((item,idx) => {    // dla każdego dnia z tablicy days
            getPage(item,(html) => {   // pobierz stronę (request) html(json string) z url zawierający dzień w item     
            let data = parsePage( html ); // wybierz tylko te dane które są potrzebne
            data.forEach((draw) => {      // dla każdej danej 
                addDrawObj(draw);      // dodaj element do tablicy zbiorczej zmienna globalna
            });
            if (timerId) clearTimeout(timerId);   // to ten mój pomysł , zeruj timer porzedni
            timerId = setTimeout(callback,2000); // ustaw nowy, jak będzie ostatni element to zadziała callback i dane są skompletowane
        });
});

symulacja na asynchronicznym readfile zamiast request jest na pastebin : https://pastebin.com/RjNFkgZ4 w pliku tekstowym jest prosty string json : [{"name": "item1"},{"name": "item2"},{"name": "item3"}] symulujacy trzy dane z jednego dnia jako odpowiedź rest api.

jak to zrobic elegancko ?.
pozdrawiam
AK

3

próbowałeś Promise.all?

0

... albo await async

0

Promise działa zarówno Promise.all jak i Promise.then ale w funkcji resolve niestety musiałem dac opóżnienie setTimeOut, króciutkie bo wyświetla wyniki od 3ms w górę. Dałem 30 ms. dla pewności. Czemu tak się dzieje nie wiem ???. Może dlatego że jest to funkcja asynchroniczna i umieszczona jest jako resolve na wyjsciu z pętli ???.

// pętla główna dla jednego kawałka tablicy
console.time('loop_time');
var days = chunks[0];
  var go = new Promise((resolve, reject) => {
        days.forEach((item) => {
            getPage(item, (html) => {
                let data = parsePage(html,item);
                    addDrawObj(data);
            });  
        });
        
        setTimeout(function() {         // <------ A JEDNAK setTimout
            resolve();
          }, 30); // działa od minimum 3ms ???
        
    });

// go.then((value) => {
//     console.log(value);
//     //console.log(outarray);
// });

Promise.all([go]).then(() => {
    if (outarray.length>0) {
        outarray.sort((a,b)=>{
            let da = new Date(a[0].date);
            let db = new Date(b[0].date);
            return da-db;
        })
        }
    outarray.forEach((element,idx) => {
        if (idx>45)
        console.log(element);
    });    
    console.log('length data:'+outarray.length);
    console.timeEnd('loop_time');
});

całość testu na paste.bin https://pastebin.com/PCjDSiM9. Await Async jeszcze nie próbowałem, bo nie bardzo wiem gdzie to umiescic. Dlatego że ta petla nie zwraca wartosci , w tej petli składana jest tablica w zmiennej globalnej. Może spróbuje zwracać warość i składać w zmiennej lokalne ?.

2

Używając Promise.all nie musisz mieć zmiennej globalnej, ponieważ to ci daje wyniki wszystkich promisów w tablicy:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

W zasadzie nawet na zmiennej globalnej by ci powinno działać (tj. jest nieelegancko, ale powinno działać), gdybyś faktycznie uruchamiał resolve po tym jak dostajesz dane, a nie setTimeout. No i jakbyś tworzył Promise dla każdego dnia, bo teraz po prostu opakowałeś w Promise wszystko.

Zamiast forEach weź zrób map i z tablicy days wyprowadź sobie tablicę, w której każdy element to Promise, który rozwiązuje się do pewnego data:

promises = days.map((item) => new Promise(resolve => {
     getPage(item, (html) => {
        let data = parsePage(html,item);
        // alternatywnie tutaj mógłbyś pakować dane do zmiennej globalnej... 
        // tylko po co?
        resolve(data); 
     });  
});
//..........
Promise.all(promises).then(values => {
});

Witam dopadły mnie te piekielne callbacki i niestety po dwu dniach poległem

W sumie nawet na zwykłych callbackach, bez promisów by się to dało zrobić (i kiedyś się robiło, jak jeszcze nie było promisów, zresztą dalej można, jeśli akurat nie chcesz/nie możesz użyć promisów).

Wystarczyłoby, że zrobiłbyś jakiś licznik, który na początku będzie równy wielkości tablicy, którą chcesz pobrać:

let daysLeft = days.length;

a potem w funkcji addDrawObj, byś zmniejszać licznik i sprawdzał czy zostały jeszcze jakieś dni do załadowania

const addDrawObj = (obj) => {
    outarray.push(obj);
    daysLeft--;
    if (daysLeft <= 0) {
         tutaj_uruchom_swoj_callback(outarray);
    }
};

Co prawda pisanie na callbackach jest męczące, dlatego ludzie szukają wygodniejszych abstrakcji, które pozwalają na ogarnięcie asynchronicznego kodu (Promise, async/await, Observable, generatory... i kto wie, co wymyślą za rok), ale jednak da się.

0

Witam dopadły mnie te piekielne callbacki

Gdy pisałem ostatnio, jakieś pół roku temu, promisy plus generator np. TEN załatwiały sprawę. Wtedy wychodzi całkiem ładny, płaski kod. Są sytuacje kiedy kod na samych promisach nie wygląda specjalnie lepiej niż na callbackach.

0

Dziękuje że poświeciłeś swój cenny czas i wyłożyłeś temat obszernie i jasno. Potrzebowałem tego bo odkrywam dopiero jak bogaty jest ten język i jakie przemyślne w nim tkwią sposoby. Zawsze JS traktowałem jako dodatek do przeglądarek taki ozdobnik, kwiatek do kożucha. A tu taka niespodzianka :).

*Bo ja już staruch jestem z takiego sortu co młodzi teraz określają "moherami" ale nie uprawiam tego "sportu" i nigdy takim się nie czułem i myślę że ta moja pasja nie pozwala mi skapcanieć. A nadziwić się nie mogę że tak licznie są ich rzesze z mojego pokolenia. Zawsze myślałem że to pokolenie to pokolenie rewolucji naukowo-technicznej, zawsze głodne postępu i wiedzy. Tym bardziej cenię pomoc młodych mądrych ludzi a wujek "google" nieraz tak powikła że nie da się z tego nic zbudować. Pamiętam jak uczyłem się C to był taki Pan Jan.B, o wielkiej wiedzy i umiejętnościach ale nie potrafił tego przekazać w opasłych stronach swoich książek. Natomiast książka o tym języku o ile pamiętam Kernighan. & Ritchie to dzieło no wręcz porywające czytelnika.
*
Sposób z licznikiem próbowałem tylko zapewne miejsce tego liczenia umieszczałem całkiem na wyjściu z całej procedury i coś ten stos Node poplątałem bo licznik liczył szybciej niż kończył
zanim złożył wyjście. Tak sobie pomyślałem że te timeouty zadziałały bo Node potrzebny jest czas na zdjęcie ze stosu ???. Spróbuje tez bo temat ciekawy składania z dziennych urobków pętli na zwrotach z resolve(value) i jeszcze spróbuje też wrócić do "zwykłych" callbacków. :)

-- Jeszcze raz, Dziękuje. Jak pozwolisz z pewnością kiedyś jeszcze zapytam :).
pozdr AK

1

Tak sobie pomyślałem że te timeouty zadziałały bo Node potrzebny jest czas na zdjęcie ze stosu ???.

Tutaj jest fajnie wytłumaczone (animacje itp.) co się dzieje pod spodem, w silniku JavaScript, w kwestii setTimeout, asynchroniczności:

0

Wskazówki pomogły, uruchomiłem sposób z licznikiem, callbackiem i składaniem lokalnym. Każdy ze sposobów działa ok. Przerażenie spowodował brak wiedzy i zrozumienia asynchronicznosci. Dzieki za wskazówki. :)
Nawet dołożyłem pętle po podzieleniu(chunk) tablicy dni a wiec doszedł jeszcze jeden forEach... i wszystkie kawałki działają. Jeżeli nie pokawałkuje się to duża ilość wywołań (próbowałem ok 600) powoduje błędy o ile pamietam TIMELIMIT i SOCKET TIMELIMIT.

console.time('loop_time');

// pętla główna z licznikiem
// zapis porcji dat do plików 
// i składanie tablicy w pętl
i
function callback (outs,idx) { 
    savePage(outs, idx ); 
    console.log(idx,' length: ',outs.length);
}

chunks.forEach((days,idx) => { 
    var outs=[];
    var itemsProcessed = 0;
        days.forEach((item,index,array) => {
       
            getPage(item, (html) => {  // funkcja asynchroniczna pobiera strony
                let data = parsePage(html,item);
                itemsProcessed++;
                outs.push(data);
                if(itemsProcessed === array.length) {
                    callback(outs,idx);
                  }                
            });
              
        });
    });

console.timeEnd('loop_time');

Asynchroniczność jednak wymaga zmiany myślenia. Teraz wydaje się proste i nie moge zrozumiec czemu dwa dni błądziłem :), dzieki jeszcze raz ...

pozdrawiam AK

1 użytkowników online, w tym zalogowanych: 0, gości: 1