iT邦幫忙

2

Fetch API資料無法按照順序排列

  • 分享至 

  • xImage

各位版友大家好,想要請教個新手問題,
這幾天在練習串接API,選擇使用pokemon api作出寶可夢圖鑑,
想要加入按照地區分類的功能,按下該地區的button後,
在畫面上呈現出該地區的寶可夢,目前的作法是用id區分,
在button放入該地區最後一隻的id,執行for loop render畫面。

DEMO

例如關都地區的寶可夢id為1~151

<button type="button" class="kanto btn" pokenum="151">關都</button>

問題:
第一次點擊按鈕,可以按照id順序排列,但多按幾次後卻會順序錯亂,
似乎為非同步的問題? 不知道該如何解決,
想請問有什麼關鍵字或是怎麼改寫比較好呢?
希望有人可以解惑,感謝

const poke_container = document.getElementById("poke_container");
const kanto = document.querySelector(".kanto");


kanto.addEventListener("click", (e) => {
  navbarbuttons.classList.remove("active");
  poke_container.innerHTML = "";
  let pokemons_number = e.target.attributes[2].value;

  const fetchPokemons = async () => {
    for (let i = 1; i <= pokemons_number; i++) {
      await getPokemon(i);
    }
  };

  const getPokemon = async (id) => {
    const url = `https://pokeapi.co/api/v2/pokemon/${id}`;
    const res = await fetch(url);
    const pokemon = await res.json();
    createPokemonCard(pokemon);
  };

  const createPokemonCard = (pokemon) => {
    const pokemonEl = document.createElement("div");
    pokemonEl.classList.add("pokemon");
    const { id, name, sprites, types } = pokemon;

    const type = types[0].type.name;
    const pokeInnerHTML = `
    <div class="img-container">
    <img src="${sprites.front_default}" alt="${name}" />
    </div>
    <div class="info>
    <span class="number">${id}</span>
    <h3 class="name">${name}</h3>
    <small class="type">Type: <span>${type}</span></small>
    </div>
  `;
    pokemonEl.innerHTML = pokeInnerHTML;
    poke_container.appendChild(pokemonEl);
  };

  fetchPokemons();
});

https://ithelp.ithome.com.tw/upload/images/20220716/20150224LTkPqrzvGh.png
https://ithelp.ithome.com.tw/upload/images/20220716/20150224P4m7jGFE3s.png

已解決,謝謝平台
解決方式: 先把資料存入array,再用appendchild過一次
(不過速度會變慢不少)
deh iT邦研究生 1 級 ‧ 2022-07-17 12:12:50 檢舉
要那樣做的話可以考慮用Promise.all(),排序問題就加個屬性最後sort就行了,第一次無快取時載入會快不少
好的,會再嘗試改寫看看,謝謝
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
2
deh
iT邦研究生 1 級 ‧ 2022-07-16 16:17:56
最佳解答

多按幾次順序會亂掉(X
第一次還沒跑完,第二次又加上去了
所以開頭你會看到插隊如下圖

然後看後面又是正常(因為上一次的都跑完了

可以click時的appendChild加到一個新的div
舊的div hide之後再處理

或者看看有什麼別的解法

原來是這個原因,謝謝解答! 會朝這方向研究一下

1
淺水員
iT邦大師 6 級 ‧ 2022-07-17 02:12:31

雖然結案了,但還是提供參考

html 部分如果要自訂屬性的話,建議加上 data-,之後 javascript 就可以用 dataset 來存取。

以下為範例:
html 部分

<ul class="topnav">
    <li class="btn-home"><a href="index.html">Home</a></li>
    <li>
        <button type="button" class="btn" data-pokenum-start="1" data-pokenum-end="3">關都</button>
    </li>
    <li>
        <button type="button" class="btn" data-pokenum-start="4" data-pokenum-end="5">城都</button>
    </li>
</ul>

javascript 部分

const navbarbuttons = document.querySelector(".topnav");

//可以考慮把事件掛載在外層容器統一處理
navbarbuttons.addEventListener("click", (e) => {
    if(!e.target.classList.contains('btn')) {
        return;
    }
    navbarbuttons.classList.remove("active");
    poke_container.innerHTML = "";
    //用 dataset 取得開始與結束的編號,這樣就不用每個按鈕都寫個別的程式碼
    let pokemons_number_start = parseInt(e.target.dataset['pokenumStart'], 10);
    let pokemons_number_end = parseInt(e.target.dataset['pokenumEnd'], 10);
    const fetchPokemons = async () => {
        const pokemons = [];
        for (let i = pokemons_number_start; i <= pokemons_number_end; i++) {
            const pokemon = await getPokemon(i);
            pokemons[i] = pokemon;
        }

        for (let i = pokemons_number_start; i <= pokemons_number_end; i++) {
            createPokemonCard(pokemons[i]);
        }
    };
    //以下與原程式碼同
});

受教了,一開始有想過如何整合重覆的功能但想不到,謝謝提供思路!

1
前端野人
iT邦新手 3 級 ‧ 2022-07-21 23:52:27

我看完先整理一下你的程式碼
首先先抓出共用邏輯,因為你的資料是固定的所以就不用多從html抓參數了,如果是動態的那你每個地區的起始id也是要從html傳

Demo

你的問題是for迴圈已經派fetch去抓資料了,但資料還沒抓完又有新迴圈開始跑了
,所以要用Promise.all,把所有資料抓完在一起append、或者是讓等資料回傳在跑下一個迭代。

主流做法是用 Promise.all 把資料都抓完再去走訪,而我使用 for await 來實作,for await 就是達到你想要的抓一次資料 append一次卡片,整體順暢感好很多,可以參考看看

// 取得地區的資料集
const fetchPokemons = async (startId, endId) => {
  const range = [];
  for (let i = startId; i <= endId; i++) {
    range.push(getPokemon(i));
  }
  // promise all 寫法
  // (await Promise.all(range)).map((pokemon) => {
  //   createPokemonCard(pokemon);
  // });
  // for await 寫法
  for await (let pokemon of range) {
    createPokemonCard(pokemon);
  }
};

謝謝,流程說明很清楚,for await第一次看到,來好好研究一番。

我要發表回答

立即登入回答