异步Redux Sagas

时间:2019-07-03 18:12:17

标签: javascript async-await redux-saga

我正在开发一个React Native应用程序,我很难让所有异步调用正常工作。

我需要做的一个大致概述是:

  1. 获取用户位置
  2. 获取用户位置后,从API中获取deals(与axios-mock-adapter模拟)
  3. 获得API结果后,将Object转换为Deal,其中包括等待以调用Google Maps API来根据文本地址获取坐标。
  4. 转换所有交易后,将Deal s分发到商店。
  5. 完成后,(重新)显示交易列表及其与用户位置的距离。

我已将async/await放在所有可能的位置,并从传奇中的yield call()开始了第3步,但事情仍然发生着混乱。这是我的代码,后面是步骤的日志记录。抱歉,代码中的数字与上面列表中的数字不完全匹配。

第一个相关的等待失败似乎发生在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 :(得分:0)

我的问题似乎是在awaitforEach

this answer中所述,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();   
  }