Niechciana zmiana wartości tablicy z zewnętrznego skryptu

0

Witam, zaczynam zabawę z JavaScriptem i po prostu nie rozumiem, dlaczego tak się dzieję.
Plik script.js wywołuje funkcję z pliku database.js, która pobiera rekordy (najlepsze wyniki):

let records = [];
function getRecords(){
	(...)
	let data = JSON.parse(this.responseText);
	data.forEach(function(x){
		records.push(x);
	});

Gdybym wyniki wykorzystywał w tym pliku, nie byłoby problemu, jednak w pliku script tablica ma już inną budowę, nie działa na nią funkcja forEach itp.
W obu plikach tuż po zdefiniowaniu tabeli umieszczam console.log i forEach (które w script efektu nie daje). Oto wyniki:
screenshot-20190128213742.png

Może ktoś mi wyjaśnić o co tu chodzi? Dlaczego ta sama zmienna nagle zmienia swoją postać?

0

Czekaj, przecież masz normalną tablicę i log jest z pliku script.js? W dodatku ma prototype Array, więc powinien być forEach. Skąd ten pierwszy console.log?

Swoją drogą brakuje mi tu jakiejś asynchroniczności. Pobieranie danych z bazy niby jak się odbywa? Nie jest przypadkiem tak, że tam (plik database.js) robisz console.log w jakimś callbacku lub .then, a tutaj próbujesz ten kod ogarnąć synchronicznie? Obstawiam, że to jest przyczyna. Wywołujesz getRecords, jak jeszcze się te rekordy nie pobrały. Używasz jakiegoś this.responseText, który może być undefined i dlatego nie masz metody forEach.

Musisz pokazać cały kod albo przynajmniej uwzględnić to, jak pobierane są rekordy i co zwraca metoda z pliku database.js, bo teraz to wróżenie z fusów.

0

Pierwszy log pochodzi z docelowego skryptu, tuż po wykonaniu funkcji getRecords().
Przesyłam całą funkcję:

let records = [];
function getRecords(){
	let ajax = new XMLHttpRequest();
	let method = "GET";
	let url = "./getRecords.php";
	let asynchronous = true;
		
	ajax.open(method, url, asynchronous);
	ajax.send();
	ajax.onreadystatechange = function(){
		if (this.readyState == 4 && this.status == 200){
			let data = JSON.parse(this.responseText);
			data.forEach(function(x){
				records.push(x);
			});
			// test
			console.log(records);
			records.forEach(function(x){
				console.log(x.points);
			});
		}
	}
}

Typeof samego responsa daje string, a responsa po JSONie - object. Finalny records tu i tu daje typ "object".

Desu napisał(a):

Czekaj, przecież masz normalną tablicę i log jest z pliku script.js? W dodatku ma prototype Array, więc powinien być forEach. Skąd ten pierwszy console.log?

Swoją drogą brakuje mi tu jakiejś asynchroniczności. Pobieranie danych z bazy niby jak się odbywa? Nie jest przypadkiem tak, że tam (plik database.js) robisz console.log w jakimś callbacku lub .then, a tutaj próbujesz ten kod ogarnąć synchronicznie? Obstawiam, że to jest przyczyna. Wywołujesz getRecords, jak jeszcze się te rekordy nie pobrały. Używasz jakiegoś this.responseText, który może być undefined i dlatego nie masz metody forEach.

Musisz pokazać cały kod albo przynajmniej uwzględnić to, jak pobierane są rekordy i co zwraca metoda z pliku database.js, bo teraz to wróżenie z fusów.

1

No to wszystko jasne. Ta funkcja rozumiem jest w pliku database.js, tak? Pokaz kod wywołujący ta funkcje.

Jeżeli zmienna records, to zmienna globalna, to będą w niej dane dopiero po pobraniu plików. Jeżeli wywołujesz getRecords w pliku script.js i później od razu próbujesz przeczytać wartość zmiennej records, to tam nic nie będzie. Będzie tam coś za pół sekundy, sekundę, albo dwie sekundy, ponieważ sam ajax jest asynchroniczny i dopiero za X czasu po wywołaniu tej metody w Twojej zmiennej się cos pojawi, bo dopiero za X czasu odpali się funkcja onreadystatechange. Musisz użyć Promise albo async/await (nie wiem ile przeglądarek to wspiera natywnie).

Jeszcze dla wyjaśnienia. W pliku database.js jest wszystko okej, bo czytasz zmienna records w czymś, co się nazywa callback (onreadystatechange). Ta funkcja się odpala dopiero jak żądanie się wykona. W pliku script.js prawdopodobnie próbujesz zrobić to synchronicznie. A typ object jest w dwóch miejscach, bo początkowa wartość to tablica - pusta, ale wciąż tablica - a tablica w js jest obiektem.

To trochę tak, jakbyś zamówił jedzenie i w pliku database.js czekasz cierpliwie aż Ci powiedzą, ze jest gotowe i dopiero jesz, a w pliku script.js próbujesz opędzlować talerz nie czekając na jedzenie.

Tutaj masz trochę lepiej i obszerniej wytłumaczone:
https://eloquentjavascript.net/11_async.html

A tutaj wspaniała seria explain like i’m five:
https://www.reddit.com/r/javascript/comments/5pxgvc/explain_javascript_promises_to_me_like_im_5/#ampf=undefined

0

Dzięki wielkie za solidne wytłumaczenie! Po takim czasie w PHP ciężko mi się przestawić na możliwość wystąpienia takiego problemu :) Dałem w callback getRecords() funkcję wyświetlającą rekordy i wszystko śmiga :) Jeszcze raz dzięki za wpojenie tej podstawowej zasady.

Desu napisał(a):

No to wszystko jasne. Ta funkcja rozumiem jest w pliku database.js, tak? Pokaz kod wywołujący ta funkcje.

Jeżeli zmienna records, to zmienna globalna, to będą w niej dane dopiero po pobraniu plików. Jeżeli wywołujesz getRecords w pliku script.js i później od razu próbujesz przeczytać wartość zmiennej records, to tam nic nie będzie. Będzie tam coś za pół sekundy, sekundę, albo dwie sekundy, ponieważ sam ajax jest asynchroniczny i dopiero za X czasu po wywołaniu tej metody w Twojej zmiennej się cos pojawi, bo dopiero za X czasu odpali się funkcja onreadystatechange. Musisz użyć Promise albo async/await (nie wiem ile przeglądarek to wspiera natywnie).

Jeszcze dla wyjaśnienia. W pliku database.js jest wszystko okej, bo czytasz zmienna records w czymś, co się nazywa callback (onreadystatechange). Ta funkcja się odpala dopiero jak żądanie się wykona. W pliku script.js prawdopodobnie próbujesz zrobić to synchronicznie. A typ object jest w dwóch miejscach, bo początkowa wartość to tablica - pusta, ale wciąż tablica - a tablica w js jest obiektem.

To trochę tak, jakbyś zamówił jedzenie i w pliku database.js czekasz cierpliwie aż Ci powiedzą, ze jest gotowe i dopiero jesz, a w pliku script.js próbujesz opędzlować talerz nie czekając na jedzenie.

Tutaj masz trochę lepiej i obszerniej wytłumaczone:
https://eloquentjavascript.net/11_async.html

A tutaj wspaniała seria explain like i’m five:
https://www.reddit.com/r/javascript/comments/5pxgvc/explain_javascript_promises_to_me_like_im_5/#ampf=undefined

0

Jako, że problem wciąż tyczy się callbacku, nie zakładam nowego wątku tylko kontynuuję ten.
W mini-grze należy na konkretnych zasadach dopasować kolejne słowo, jeśli spełnia ono warunki, lecz nie znajduje się w bazie, wyświetla się przycisk "Dodaj słowo". Po kliknięciu słowo dodaje się (wow :)), funkcja sprawdzająca słowo wywołuje się ponownie i akceptując słowo przechodzi do kolejnej rundy.
I tutaj problem leży w tym, że baza słów nie zdąża się uzupełnić przed ponownym sprawdzeniem, w efekcie czego znów pojawia się info o braku tego słowa w bazie.
Próbowałem różnych wariantów z callbackami, obecna wersja jest najodpowiedniejsza, z tym że skrypt czasem odpowiednio przeskakuje dalej, a czasem brakuje mu słowa w bazie (trzeba kliknąć drugi raz). W moim mniemaniu funkcja sprawdzająca wywołuje się w callbacku funkcji wczytującej bazę (a więc po niej), ale jak widać wcale tak nie jest.

// Kliknięcie przycisku
$(".addWord").click(function(){
	flag = false;
	// dodanie słowa do bazy
	addWord(myWord.val(), getWordsBase);
	message.html("Słowo zostało dodane do słownika.");
	getWordsBase(checkWord); // Funkcja wczytania bazy dla pewności jest zaimplementowana po dodaniu słowa i przed ponownym sprawdzeniem
});
// pobranie bazy słów
function getWordsBase(callback = false){
	(...)
	ajax.onreadystatechange = function(){
		if (this.readyState == 4 && this.status == 200){
			let data = JSON.parse(this.responseText);
			data.forEach(function(x){
				wordsBase.push(x.word);
			});		
			if (callback){	
				callback(); // Sprawdzenie słowa (checkWord)
			}
		}
	}
};
// dodanie słowa do bazy
function addWord(x, callback){
	let url = "./addWord.php";
	let data = {word: x};
	$.post(url,data);
	callback(); //getWordsBase
}

Myślę, że szkielet funkcji checkWord jest tu zbędny, jeśli nie to podeślę. Nie wiem czy gdzieś jest jakaś gafa czy po prostu dalej coś do mnie nie dociera :)

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