• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

question-closed Google Books - API

VPS Starter Arubacloud
0 głosów
789 wizyt
pytanie zadane 6 czerwca 2020 w JavaScript przez Allen Obywatel (1,010 p.)
zamknięte 7 czerwca 2020 przez Allen

Postępuje zgodnie z dokumentacją Googla: https://developers.google.com/books/docs/viewer/developers_guide

Zmienna isbn zawiera poprawny identyfikator, wydaje mi ię że cała reszta również jest w porządku. Pojawia się taki błąd: 

readBtns.forEach(btn => {
  btn.addEventListener('click', (event) => {
    let title = event.path[2].querySelector('.title-ex').textContent;
    let gbUri = 'https://www.googleapis.com/books/v1/volumes?key=MY_KEY&cx=017576662512468239146:omuauf_lfve&q=' + title;
    fetch(gbUri).then(response => response.json()).then(response => {
      //let gbId = response.items[0].id;
      let isbn = response.items[0].volumeInfo.industryIdentifiers[1].identifier;
      google.books.load();
      google.books.setOnLoadCallback(initialize(isbn));
    });
  });
});

function initialize(isbn) {
  const viewerCanvas = document.createElement('div');
  viewerCanvas.id = 'viewerCanvas';
  const viewer = new google.books.DefaultViewer(viewerCanvas);
  viewer.load('ISBN:' + isbn, BookNotLoaded, BookLoaded);
};

 

komentarz zamknięcia: Otrzymałem odpowiedź

1 odpowiedź

+1 głos
odpowiedź 6 czerwca 2020 przez ScriptyChris Mędrzec (190,190 p.)
wybrane 6 czerwca 2020 przez Allen
 
Najlepsza

Postępuje zgodnie z dokumentacją Googla:

google.books.setOnLoadCallback(initialize(isbn));

Chyba nie do końca ;) W dokumentacji do metody setOnLoadCallback przekazywana jest referencja do funkcji initialize a nie wynik jej wywołania:

google.books.setOnLoadCallback(initialize);

Jeśli chcesz przekazać dodatkowy parametr, to możesz przekazać funkcję anonimową:

google.books.setOnLoadCallback(() => initialize(isbn));

P.S. Na przyszłość, nie umieszczaj kluczy do API w kodzie, który wstawiasz w jakiekolwiek publiczne miejsca w sieci - zastąpiłem ten fragment kodu w pytaniu frazą MY_KEY.

komentarz 6 czerwca 2020 przez Allen Obywatel (1,010 p.)
Wielkie dzięki za pomoc. Faktycznie zapomniałem o tym kluczu.
komentarz 6 czerwca 2020 przez Allen Obywatel (1,010 p.)

Zmieniłem jedynie tę funkcję anonimową. Teraz zamiast poprzedniego pojawił się taki błąd:

komentarz 6 czerwca 2020 przez ScriptyChris Mędrzec (190,190 p.)

Zmieniłem jedynie tę funkcję anonimową.

Nie rozumiem, zmieniłeś funkcję anonimową na co? :) Pokaż kod po zmianach. 

komentarz 6 czerwca 2020 przez Allen Obywatel (1,010 p.)

Użyłem funkcji anonimowej, żeby przekazać parametr isbn. 

readBtns.forEach(btn => {
  btn.addEventListener('click', (event) => {
    let title = event.path[2].querySelector('.title-ex').textContent;
    let gbUri = 'https://www.googleapis.com/books/v1/volumes?key=MY_KEY&cx=017576662512468239146:omuauf_lfve&q=' + title;
    fetch(gbUri).then(response => response.json()).then(response => {
      //let gbId = response.items[0].id;
      let isbn = response.items[0].volumeInfo.industryIdentifiers[1].identifier;
      google.books.load();
      google.books.setOnLoadCallback(() => initialize(isbn));
    });
  });
});
 
function initialize(isbn) {
  const viewerCanvas = document.createElement('div');
  viewerCanvas.id = 'viewerCanvas';
  const viewer = new google.books.DefaultViewer(viewerCanvas);
  viewer.load('ISBN:' + isbn, BookNotLoaded, BookLoaded);
};

 

komentarz 6 czerwca 2020 przez ScriptyChris Mędrzec (190,190 p.)

Wątpliwe, żeby po zmianie linijki 9 (zamiast wyniku wywołania funkcji initialize przekazujesz teraz anonimową funkcję) przestała działać linijka wyżej. Czy nie robiłeś poza tym innych zmian (może w między czasie, po założeniu tematu)?

Czy ten błąd dotyczy linijki 8, czy jeszcze gdzieś użyłeś google.books.load?

komentarz 6 czerwca 2020 przez Allen Obywatel (1,010 p.)
Funkcja load() wykonuje się w linii 8 oraz 18. Nie zmieniałem nic oprócz tej funkcji anonimowej.
komentarz 6 czerwca 2020 przez ScriptyChris Mędrzec (190,190 p.)

Metoda viewer.load z 18 linijki, to inna metoda.

Ta sytuacja jest dziwna. :) Zakomentuj 9 linijkę,czyli:

// google.books.setOnLoadCallback(() => initialize(isbn));

i sprawdź, czy błąd ustąpi.

W dokumentacji przed użyciem google.books.load jest podpięcie skryptu jsapi.js. Czy zrobiłeś to?

Tak, czy inaczej, ten błąd powinien występować wcześniej, bo google.books.load jest wołane przed google.books.setOnLoadCallback - więc może go wcześniej przeoczyłeś? Jeśli występował wcześniej, to google.books.setOnLoadCallback nie powinno być wtedy wołane.

komentarz 6 czerwca 2020 przez Allen Obywatel (1,010 p.)
Skrypt jsapi.js miałem podpięty od początku. Zakomentowałem linię 9 i nadal pojawia się ten sam błąd z linii 8. Wczoraj faktycznie raz czy dwa pojawił się ten sam błąd, lecz zająłem się kodem w zupełnie innym pliku i dzisiaj już go nie było (tzn. pojawił się ten dotyczący konstruktora i google.books.DefaultViewer).
komentarz 6 czerwca 2020 przez ScriptyChris Mędrzec (190,190 p.)
Możesz pokazać cały kod (łącznie z podpięciami skryptów w HTML)? Jeśli masz dodane jeszcze inne klucze API, to pamiętaj żeby ich nie podawać. ;)
komentarz 6 czerwca 2020 przez ScriptyChris Mędrzec (190,190 p.)

Sprawdź jeszcze w zakładce Network devtoolsów, czy skrypty Google'a się załadowały. Może coś je zablokowało, albo nie załadowały się prawidłowo?

komentarz 6 czerwca 2020 przez Allen Obywatel (1,010 p.)

Plik libary.ejs:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://fonts.googleapis.com/css2?family=Esteban&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
    <link rel="shortcut icon" type="image/png" href="./img/favicon.png">
    <link href= "library.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="https://www.google.com/books/jsapi.js"></script>
    <title>Library</title>
</head>
<body>
  <div class="container">
    <h3>My Library</h3>
    <div class="msg-banner"></div>
    <div class="examples-viewer">
      <% if(books){ %>
        <% books.forEach((book) => { %>
          <div class="example">
            <div class="thumb-space">
              <div class="thumb-cover">
                <img src="<% book.coverImageName %>" alt="Unloaded cover">
              </div>
            </div>
            <div class="description">
              <div class="title-ex"><%= book.title %>.  <%= book.subtitle ? book.subtitle + '.' : '' %></div>
              <div class="author-ex"><%= book.authorDetails %></div>
              <div class="genre-ex"><%= book.genreOfBook ? book.genreOfBook : '' %></div>
              <div class="rate-ex"><%= book.rate %> / 5</div>
              <div class="pages-ex"><%= book.numberOfPages %></div>
            </div>
            <div class="btns">
              <form action="/library/<%= book._id %>" method="DELETE" class="delete-btn-form">
                <div class="delete-btn"></div>
              </form>
              <div class="read-btn"></div>
            </div>
          </div>
          <% })} %>
    </div>
    <form action="/add" method="POST" class="back-to-add-btn">
      <input type="button" class="go-to-add" value="Add new book">
    </form>
  </div>
  <script type="text/javascript" src="libraryPage.js"></script>
</body>
</html>

Plik libraryPage.js:

const deleteBtns = document.querySelectorAll('.delete-btn-form');
const readBtns = document.querySelectorAll('.read-btn');

deleteBtns.forEach(btn => {
    btn.addEventListener('click', () => {
      btn.submit();
    });
  });

readBtns.forEach(btn => {
  btn.addEventListener('click', (event) => {
    let title = event.path[2].querySelector('.title-ex').textContent;
    let gbUri = 'https://www.googleapis.com/books/v1/volumes?key=MY_KEY&cx=017576662512468239146:omuauf_lfve&q='+ title;
    fetch(gbUri).then(response => response.json()).then(response => {
      let isbn = response.items[0].volumeInfo.industryIdentifiers[1].identifier;
      google.books.load();

      google.books.setOnLoadCallback(() => initialize(isbn));
    });
  });
});

function initialize(isbn) {
    const viewerCanvas = document.createElement('div');
    viewerCanvas.id = 'viewerCanvas';
    const container = document.querySelector('.container');
    container.appendChild(viewerCanvas);
    const viewer = new google.books.DefaultViewer(viewerCanvas);
    viewer.load('ISBN:' + isbn, bookNotLoaded, bookLoaded);
};

function bookNotLoaded () {
  console.log('Unable to load book');
}

function bookLoaded () {
  console.log('The book has loaded successfully');
  }

 

komentarz 6 czerwca 2020 przez Allen Obywatel (1,010 p.)
W network status code jsapi.js wynosi 302, czyli wg wikipedii "Znaleziono – żądany zasób jest chwilowo dostępny pod innym adresem a przyszłe odwołania do zasobu powinny być kierowane pod adres pierwotny", tylko co to właściwie znaczy? Jest to jakaś przerwa ze strony googla? Nie znalazłem na ten temat nic w dokumentacji.
komentarz 6 czerwca 2020 przez ScriptyChris Mędrzec (190,190 p.)

tylko co to właściwie znaczy

To, co przeczytałeś na Wikipedii.

Jeśli request zwraca 302, to w nagłówku odpowiedzi (pod kluczem location) powinien być podany adres, pod którym dany zasób jest teraz dostępny - przeglądarka sama wysyła następny request pod ten "nowy" adres. Sprawdź w Network, czy zostało wysłane dodatkowe zapytanie na ten URL i jaka jest odpowiedź.

Sprawdziłem na świeżym CodePen i skrypt jsapi.js jest pobierany oraz funkcja google.books.load jest dostępna i wywołuje się.

https://codepen.io/JSHolic/pen/XWXbagq

komentarz 6 czerwca 2020 przez Allen Obywatel (1,010 p.)
Dodatkowe zapytanie, na nowy adres z location, zostało wysłane a kod odpowiedzi wynosi 200, więc wszytko się zgadza. Niestety błąd dalej nie znika.
komentarz 6 czerwca 2020 przez ScriptyChris Mędrzec (190,190 p.)

Ok, a czy w odpowiedzi na to drugie zapytanie jest kod skryptu z metodą load? Co pokazuje konsola, jeśli wypiszesz do niej google.books - co zawiera ten obiekt (tuż przed próbą wywołania metody google.books.load)?

komentarz 6 czerwca 2020 przez Allen Obywatel (1,010 p.)

Odpowiedź na drugie zapytanie:

Obiekt google.books zawiera wiele różnych danych:

komentarz 6 czerwca 2020 przez ScriptyChris Mędrzec (190,190 p.)

Hmm, a jeśli w HTML umieścisz skrypt z console.log(google.books.load) tuż pod podpięciem jsapi.js, to czy wtedy jest ta funkcja dostępna? Wygląda na to, że zanim wykona się kod obsługujący kliknięcie w przycisk i Promise zwrócony z zapytania po książkę będzie obsłużony w then, to obiekt google.books zmienia swoją strukturę.

Jeśli metoda google.books.load będzie dostępna tuż pod podpięciem API, to znaczy że należy załadować API wcześniej, a nie dopiero w odpowiedzi na jakieś asynchroniczne akcje w aplikacji.

komentarz 7 czerwca 2020 przez Allen Obywatel (1,010 p.)
edycja 7 czerwca 2020 przez Allen

console.log(google.books.load()) podpięte tuż pod jsapi.ja pokazuje, że funkcja jest dostępna.

const deleteBtns = document.querySelectorAll('.delete-btn-form');
const readBtns = document.querySelectorAll('.read-btn');
 
deleteBtns.forEach(btn => {
    btn.addEventListener('click', () => {
      btn.submit();
    });
  });
 
readBtns.forEach(btn => {
  btn.addEventListener('click', (event) => {
    google.books.load();
    let title = event.path[2].querySelector('.title-ex').textContent;
    let gbUri = 'https://www.googleapis.com/books/v1/volumes?key=MY_KEY&cx=017576662512468239146:omuauf_lfve&q='+ title;
    fetch(gbUri).then(response => response.json()).then(response => {
      let isbn = response.items[0].volumeInfo.industryIdentifiers[1].identifier;
 
      google.books.setOnLoadCallback(() => initialize(isbn));
    });
  });
});
 
function initialize(isbn) {
    const viewerCanvas = document.createElement('div');
    viewerCanvas.id = 'viewerCanvas';
    const container = document.querySelector('.container');
    container.appendChild(viewerCanvas);
    const viewer = new google.books.DefaultViewer(viewerCanvas);
    viewer.load('ISBN:' + isbn, bookNotLoaded, bookLoaded);
};
 
function bookNotLoaded () {
  console.log('Unable to load book');
}
 
function bookLoaded () {
  console.log('The book has loaded successfully');
  }

Teraz występuje błąd: google.books.setOnLoadCallback is not a function. Gdy linię 18 przeniosę pod 38 zmienna isbn nie jest dostępna.

komentarz 7 czerwca 2020 przez ScriptyChris Mędrzec (190,190 p.)

Hmm..., trudno mi powiedzieć dlaczego tak się dzieje. Wychodzi na to, że w późniejszym etapie działania aplikacji obiekt google.books zmienia swoją strukturę, stąd na etapie obsługi Promisa nie ma dostępu do metod load ani setOnLoadCallback. Żeby znaleźć przyczynę, trzeba by debugować kod.

Może wystarczy funkcję initialize zawołać raz, a potem wołać viewer.load z kolejnymi wartościami isbn operując na referencji viewer i już nie wołać google.books.[load/setOnLoadCallback] więcej niż raz?


Zadam trochę głupie pytanie, ale ile razy wywołujesz setOnLoadCallback? To dzieje się na klik - za którym kliknięciem to nie działa?

komentarz 7 czerwca 2020 przez Allen Obywatel (1,010 p.)
edycja 10 czerwca 2020 przez Allen

Gdy wywołuję funkcję google.books.load() oraz google.books.setOnLoadCallback(initialize), to element viewer jest niezdefiniowany. 

readBtns.forEach(btn => {
  btn.addEventListener('click', (event) => {
    google.books.load();
    console.log('Pierwszy');
    let title = event.path[2].querySelector('.title-ex').textContent;
    let gbUri = 'https://www.googleapis.com/books/v1/volumes?key=MY_KEY&cx=017576662512468239146:omuauf_lfve&q=' + title;
    fetch(gbUri).then(response => response.json()).then(response => {
      let isbn = response.items[0].volumeInfo.industryIdentifiers[1].identifier;
      google.books.setOnLoadCallback(() => initialize(isbn));
    });

    const bookNotLoaded = () => {
    console.log('Unable to load book');
      const fillMsg = document.createElement('div');
      fillMsg.className = 'warningMsg';
      const fillTextMsg = document.createTextNode('Unable to load book');
      fillMsg.appendChild(fillTextMsg);
      const contentModal = document.querySelector('.msg-banner');
      contentModal.appendChild(fillMsg);
    };
    
    const bookLoaded = () => {
    console.log('The book has loaded successfully');
      const warningMsg = document.querySelector('.warningMsg');
      if (warningMsg !== null) {
        warningMsg.parentNode.removeChild(warningMsg);
      }
    };

    const initialize = (isbn) => {
      console.log('Drugie');
      const viewerCanvas = document.createElement('div');
      viewerCanvas.id = 'viewerCanvas';
      const container = document.querySelector('.container');
      container.appendChild(viewerCanvas);
      const viewer = new google.books.DefaultViewer(viewerCanvas);
      viewer.load('ISBN:' + isbn, bookNotLoaded, bookLoaded);
    };
  });
});

W tym kodzie po pierwszym kliknięciu w konsoli pojawia się "Pierwszy", a błąd wyskakuje po drugim i każdym następnym kliknięciu. Gdy po odświeżeniu strony szbko klikam wiele razy w readBtn w konsoli pojawia się kilka razy "Pierwszy" i dopiero błąd.

komentarz 7 czerwca 2020 przez ScriptyChris Mędrzec (190,190 p.)

Przenieś google.books.load() na początek kodu - niech to się wykonuje raz w trakcie działania aplikacji (a nie przy każdym kliku, jak teraz).

Gdy wywołuję funkcję google.books.load() oraz google.books.setOnLoadCallback(initialize), to element viewer jest niezdefiniowany

W którym miejscu viewer jest niezdefiniowany? Tą zmienną tworzysz w funkcji initialize i tylko tu masz teraz do niej dostęp.

Wywołaj funkcję initialize po google.books.load(), ale zmienną viewer trzymaj w zewnętrznym scope - niech w funkcji initialize będzie tylko zainicjowana przez new google.books.DefaultViewer. Dopiero na kliknięcie wołaj funkcję, do której przekażesz isbn i tam dopiero wołaj viewer.load z przekazaniem tego isbn (na takiej zasadzie jak robisz teraz, z tym że inicjuj API tylko raz, na początku).

Coś takiego:

    let viewer = null;
    
    google.books.load();
    google.books.setOnLoadCallback(initialize);

    function initialize () {
      const viewerCanvas = document.createElement('div');
      viewerCanvas.id = 'viewerCanvas';
      const container = document.querySelector('.container');
      container.appendChild(viewerCanvas);
      viewer = new google.books.DefaultViewer(viewerCanvas);
    };

   function loadPage (isbn) {
     viewer.load('ISBN:' + isbn, bookNotLoaded, bookLoaded);
   }

   /* kod obsługujący Promisa zwróconego przez fetch*/
   .then(response => {
      let isbn = response.items[0].volumeInfo.industryIdentifiers[1].identifier;
      loadPage(isbn);
   })
komentarz 7 czerwca 2020 przez Allen Obywatel (1,010 p.)
Wszytko działa. Bardzo dziękuję za pomoc i poświęcony czas, w życiu bym tego sam nie ogarnął.

Podobne pytania

0 głosów
0 odpowiedzi 114 wizyt
pytanie zadane 7 czerwca 2020 w Android, Swift, Symbian przez Paweł123 Nałogowiec (33,500 p.)
0 głosów
1 odpowiedź 239 wizyt
pytanie zadane 19 listopada 2018 w JavaScript przez niezalogowany
0 głosów
2 odpowiedzi 1,044 wizyt
pytanie zadane 26 maja 2020 w JavaScript przez frederrer Użytkownik (740 p.)

93,023 zapytań

141,986 odpowiedzi

321,288 komentarzy

62,369 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj

Wprowadzenie do ITsec, tom 2

Można już zamawiać tom 2 książki "Wprowadzenie do bezpieczeństwa IT" - będzie to około 650 stron wiedzy o ITsec (17 rozdziałów, 14 autorów, kolorowy druk).

Planowana premiera: 30.09.2024, zaś planowana wysyłka nastąpi w drugim tygodniu października 2024.

Warto preorderować, tym bardziej, iż mamy dla Was kod: pasja (użyjcie go w koszyku), dzięki któremu uzyskamy dodatkowe 15% zniżki! Dziękujemy zaprzyjaźnionej ekipie Sekuraka za kod dla naszej Społeczności!

...