Асинхронные Redux Sagas - PullRequest
0 голосов
/ 03 июля 2019

Я работаю над приложением React Native, и мне очень трудно заставить все мои асинхронные вызовы работать правильно.

Примерно то, что мне нужно, это:

  1. Получить местоположение пользователя
  2. После получения местоположения пользователя, получить deals из API (поцеловано с axios-mock-adapter)
  3. После получения результатов API преобразуйте Object с в Deal с, что включает ожидание для вызова API Карт Google для получения координат на основе текстовый адрес.
  4. После конвертирования всех сделок отправьте Deal s в магазин.
  5. После того, как все это сделано, (пере) отобразите список сделок с их расстоянием от местоположения пользователя.

Я поместил async/await s везде, где смогу, и начал шаг 3 с yield call() в моей саге, но все еще происходит не по порядку. Вот мой код, а затем журнал шагов, как они происходят. Извините, цифры в коде не совсем совпадают с числами в моем списке выше.

Похоже, что первая соответствующая ошибка ожидания происходит в Deal.collectionFromArray или в forEach обратном вызове. Хотя await getLocationAsync() в AppNavigator.js также не может ждать в первую очередь.

Кто-нибудь может помочь понять, почему они не работают и не ждут? Большое спасибо.

Шаг 1, начало шага 2. AppNavigator.js (отрывок)

const TabNavigator = createBottomTabNavigator(
  {
    Map: MapStack,
    List: ListStack
  },
  { initialRouteName }
);

class TabContainer extends Component<Object> {
  static router = TabNavigator.router;
  async componentDidMount() {  
    console.log("getting location");
    await this.props.getLocationAsync();      <-- await
    console.log("location:", this.props.location);
    console.log("getting deals");
    await this.props.getDeals();      <-- await
  }
  render() {
    return <TabNavigator navigation={this.props.navigation} />;
  }
}
const SwitchNavigator = createSwitchNavigator({
  // Auth: AuthNavigator,
  Main: connect(
    ({ location }) => ({ location: location.currentRegion }),
    { getLocationAsync, getDeals }
  )(TabContainer)
});

const AppNavigator = createAppContainer(SwitchNavigator);

type AppProps = {
  navigation: Object,
  getLocationAsync: () => void,
  loadingMessage: string
};

class AppContainer extends Component<AppProps> {
  static router = TabNavigator.router;

  componentDidMount() {
    // this.props.getLocationAsync();
  }

  render() {
    return (
      <View style={{ flex: 1 }}>
        <LoadingIndicator message={this.props.loadingMessage} />
        <AppNavigator navigation={this.props.navigation} />
      </View>
    );
  }
}

export default connect(({ location, deals, auth }) => ({
  loadingMessage:
    auth.loadingMessage || location.loadingMessage || deals.loadingMessage
}))(AppContainer);

Шаг 2 и 4, dealActions.js

export async function getDealsApi(): Promise<Object[] | Error> {
  try {
    const res = await axios.get(ApiUrls.getProductsAuthorized);      <-- await
    console.log("2. API got", res.data.length, "deals (from getDealsApi)");
    const deals = await Deal.collectionFromArray(res.data);      <-- await
    console.log("3. converted", Object.keys(deals).length, "deals with Deal.fromApi (from getDealsApi)" );
    return deals;
  } catch (error) {
    console.warn("get products failed. is axios mock adapter running?");
    return error;
  }
}

export function* getDealsSaga(): Saga<void> {
  try {
    console.log("1. calling API (from getDealsSaga)");
    const deals: DealCollection = yield call(getDealsApi);      <-- yield call()
    console.log("4. dispatching", Object.keys(deals).length, "deals (from getDealsSaga)" );
    yield put({ type: "GET_DEALS_SUCCESS", deals });
  } catch (error) {
    console.warn("getDealsSaga", "error:", error);
    yield put({ type: "GET_DEALS_FAILURE", error });
  }
}

Шаг 3, Deal.js

static async collectionFromArray(array: Object[]) {
    const deals: DealCollection = {};
    await array.forEach(async p => { .      <--await
      deals[p.id] = await Deal.fromApi(p);   <--await
    });
    console.log("converted:", Object.keys(deals).length);
    return deals;
  }

static async fromApi(obj: Object): Promise<Deal> {
    const deal = new Deal();
    deal.id = obj.id;
    deal.name = obj.name;
    // ...set other properties

    if (deal.address) {
      const location = await Deal.getLocationForAddress(deal.address);   <-- await
      deal.location = location;
    }
    // console.log(deal.location);

    return deal;
  }

  static async getLocationForAddress(address: string): Promise<?Location> {
    try {
      const search = await fetch(ApiUrls.mapsSearch(address));   <-- await
      const { predictions, ...searchData } = await search.json();   <-- await
      if (searchData.error_message) throw Error(searchData.error_message); 
      if (!predictions.length) throw Error("No places found for " + address);
      const details = await fetch(ApiUrls.mapsDetails(predictions[0].place_id));   <-- await
      const { result, ...detailsData } = await details.json();   <-- await
      if (detailsData.error_message) throw Error(searchData.error_message);
      const location = result.geometry.location;
      return {
        latitude: location.lat,
        longitude: location.lng
      };
    } catch (error) {
      console.log("Error getting location for address in deal:", error);
    }
  }

Оставляя код для шага 5, довольно очевидно.

Журналы с комментариями:

getting location
location: null     // even this first call isn't waiting!
getting deals
1. calling API (from getDealsSaga)
2. API got 10 deals (from getDealsApi()) // will this wait when we're calling the actual web API?
converted: 0    // not waiting
3. converted 0 deals with Deal.fromApi (from getDealsApi)  // previous log didn't wait so of course this is empty
4. dispatching 0 deals (from getDealsSaga)   // ditto. perhaps yield call() is working but all the non-waiting above makes that irrelevant
// after all this is done, the Google Maps calls finally return, nothing is dispatched or rerendered.
lat: 40.78886889999999, long: -73.9745318
lat: 40.78886889999999, long: -73.9745318
lat: 40.78886889999999, long: -73.9745318
// ... and the rest of the coordinates, finally returned

1 Ответ

0 голосов
/ 04 июля 2019

Похоже, моя проблема была await внутри forEach.

Как упоминалось в , этот ответ , await внутри forEach не работает, как я ожидал.Использование for...in, хотя и чуть более многословно, работает:

  // in Deal.js
  async setLocation() {
    this.location = await Deal.getLocationForAddress(this.address);
  }

  // inside of getDealsApi()
  for (const id in deals) {
    const deal = deals[id];
    if (!deal.location && deal.address) await deal.setLocation();   
  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...