Я работаю над приложением React Native, и мне очень трудно заставить все мои асинхронные вызовы работать правильно.
Примерно то, что мне нужно, это:
- Получить местоположение пользователя
- После получения местоположения пользователя, получить
deals
из API (поцеловано с axios-mock-adapter
)
- После получения результатов API преобразуйте
Object
с в Deal
с, что включает ожидание для вызова API Карт Google для получения координат на основе текстовый адрес.
- После конвертирования всех сделок отправьте
Deal
s в магазин.
- После того, как все это сделано, (пере) отобразите список сделок с их расстоянием от местоположения пользователя.
Я поместил 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