nested asyn c - лучшие практики - PullRequest
0 голосов
/ 10 марта 2020

Я делаю фиктивное приложение для тестирования серверного API. Первый запрос возвращает вложенный объект JSON с именами продукта и количеством имеющихся у него вариантов. Оттуда я извлекаю название продукта, чтобы я мог отправить второй запрос для получения списка вариантов с изображениями продуктов, размерами и т. Д. c.

first request

second request

Иногда он будет загружать и отображать варианты только из одного продукта, но в большинстве случаев он будет работать правильно и загружать все варианты из обоих фиктивных продуктов. Есть ли лучший способ сделать это, чтобы убедиться, что он работает стабильно хорошо. Также я хотел бы знать, есть ли лучший общий подход к написанию чего-то подобного.

Вот код:

  import React, { useEffect, useState } from "react";
import axios from "axios";

import ShirtList from "../components/ShirtList";

const recipeId = "15f09b5f-7a5c-458e-9c41-f09d6485940e";

const HomePage = props => {
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    axios
      .get(
        `https://api.print.io/api/v/5/source/api/prpproducts/?recipeid=${recipeId}&page=1`
      )
      .then(response => {
        let shirtList = [];
        const itemsLength = response.data.Products.length;
        response.data.Products.forEach((element, index) => {
          axios
            .get(
              `https://api.print.io/api/v/5/source/api/prpvariants/?recipeid=${recipeId}&page=1&productName=${element.ProductName}`
            )
            .then(response => {
              shirtList.push(response.data.Variants);
              if (index === itemsLength - 1) {
                setLoaded(shirtList);
              }
            });
        });
      });
  }, []);

  const ListItems = props => {
    if (props.loaded) {
      return loaded.map(item => <ShirtList items={item} />);
    } else {
      return null;
    }
  };

  return (
    <div>
      <ListItems loaded={loaded} />
    </div>
  );
};

export default HomePage;

Ответы [ 2 ]

2 голосов
/ 10 марта 2020

Вы устанавливаете загруженные рубашки после каждой итерации, так что вы будете получать только последние разрешенные данные обещания, вместо этого извлеките все данные и затем обновите состояние.

Кроме того, отделите свое состояние, одно для загрузки состояние и один для данных.

Вариант 1 с использованием async/await

const recipeId = '15f09b5f-7a5c-458e-9c41-f09d6485940e'
const BASE_URL = 'https://api.print.io/api/v/5/source/api'

const fetchProducts = async () => {
  const { data } = await axios.get(`${BASE_URL}/prpproducts/?recipeid=${recipeId}&page=1`)
  return data.Products
}

const fetchShirts = async productName => {
  const { data } = await axios.get(
    `${BASE_URL}/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`,
  )
  return data.Variants
}

const HomePage = props => {
  const [isLoading, setIsLoading] = useState(false)
  const [shirtList, setShirtList] = useState([])

  useEffect(() => {
    setIsLoading(true)

    const fetchProductShirts = async () => {
      const products = await fetchProducts()
      const shirts = await Promise.all(
        products.map(({ productName }) => fetchShirts(productName)),
      )
      setShirtList(shirts)
      setIsLoading(false)
    }

    fetchProductShirts().catch(console.log)
  }, [])
}

Вариант 2 с использованием необработанных обещаний

const recipeId = '15f09b5f-7a5c-458e-9c41-f09d6485940e'
const BASE_URL = 'https://api.print.io/api/v/5/source/api'

const fetchProducts = () =>
  axios.get(`${BASE_URL}/prpproducts/?recipeid=${recipeId}&page=1`)
    .then(({ data }) => data.Products)


const fetchShirts = productName =>
  axios
    .get(
      `${BASE_URL}/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`,
    )
    .then(({ data }) => data.Variants)

const HomePage = props => {
  const [isLoading, setIsLoading] = useState(false)
  const [shirtList, setShirtList] = useState([])

  useEffect(() => {
    setIsLoading(true)

    fetchProducts
      .then(products) =>
        Promise.all(products.map(({ productName }) => fetchShirts(productName))),
      )
      .then(setShirtList)
      .catch(console.log)
      .finally(() => setIsLoading(false)
  }, [])
}

Теперь у вас есть состояние isLoading для состояние загрузки и shirtList для данных, которые вы можете отобразить, основываясь на этом, как это

return (
  <div>
    {isLoading ? (
      <span>loading...</span>
      ) : (
      // always set a unique key when rendering a list.
      // also rethink the prop names
      shirtList.map(shirt => <ShirtList key={shirt.id} items={shirt} />)
    )}
  </div>
)

Ссылки

Promise.all

Promise.prototype.finally

Реактивный ключ реквизита

1 голос
/ 10 марта 2020

Следующее должно передать плоский массив всех вариантов (для всех продуктов) в setLoaded. Я думаю, что это то, что вы хотите.

Как только все продукты получены, мы сопоставляем их с массивом обещаний для получения вариантов.

Мы используем Promise.allSettled, чтобы ждать всех варианты для извлечения, а затем мы объединяем результат в один массив.

useEffect(()=>(async()=>{
        const ps = await getProducts(recipeId)
        const variants = takeSuccessful(
            await Promise.allSettled(
                ps.map(({ProductName})=>getVariants({ recipeId, ProductName }))))
        setLoaded(variants.flat())
    })())

... и вам понадобятся вспомогательные функции, подобные этим:

const takeSuccessful = (settledResponses)=>settledResponses.map(({status, value})=>status === 'fulfilled' && value)
const productURL = (recipeId)=>`https://api.print.io/api/v/5/source/api/prpproducts/?recipeid=${recipeId}&page=1`
const variantsURL = ({recipeId, productName})=>`https://api.print.io/api/v/5/source/api/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`

const getProducts = async(recipeId)=>
    (await axios.get(productURL(recipeId)))?.data?.Products

const getVariants = async({recipeId, productName})=>
    (await axios.get(variantsURL({recipeId,productName})))?.data?.Variants
...