简体   繁体   中英

async function triggers setstate before await is complete

setLoading gets called before all the data gets fetched. How do I ensure that setLoading is called only after the for loop that fetches the data is completed? Also is it even advisable to combine async data with non async data? What I'm trying to achieve is to render a timeline with each event accompanied with their respective directions(which is the async data)

const Schedule = (props) => {
const [events, setEvents] = React.useState([]);
const [visible, setVisible] = React.useState(false);
const [unsatisfied, setUnsatisfied] = React.useState("");
const [timingsArray, setTimingsArray] = React.useState([]);
const [isLoading, setLoading] = React.useState(true);


React.useEffect(() => {
            directionsArray(props.initRoutes, props.timings, props.data);
        }, []);

const directionsArray = async (allRoutes, timings, data) => {
    let result = [];

    for (let i = 0; i < allRoutes.length - 1; i++) {
        let obj = { distance: "", duration: "", steps: [] };
        let steps = [];
        let distance = "";
        let duration = "";
        let origin =
            typeof allRoutes[i] === "object"
                ? allRoutes[i].lat + "," + allRoutes[i].long
                : allRoutes[i];
        let destination = allRoutes[i + 1];
        try {
            let resp = await fetch(
                "https://maps.googleapis.com/maps/api/directions/json?origin=" +
                    origin +
                    "&destination=" +
                    destination +
                    "&key=" +
                    GOOGLE_MAPS_API_KEY +
                    "&mode=transit&region=sg"
            );
            //console.log(JSON.stringify(await resp.json()));
            let data = (await resp.json())["routes"][0]["legs"][0];
            let response = data["steps"];
            distance = data["distance"]["text"];
            duration = data["duration"]["text"];

            for (let j = 0; j < response.length; j++) {
                steps.push(await routeFormatter(await response[j]));
            }
        } catch (err) {
            console.log(err);
        }
        obj.steps = steps;
        obj.distance = distance;
        obj.duration = duration;
        result.push(obj);
    }
    let updatedTimings = merge(timings, result);
    let combinedData = eventsWithDirections(updatedTimings, data, result);
    setTimingsArray(updatedTimings);
    setEvents(combinedData);
    setLoading(false);
};

if (isLoading) {
        return (
            <View
                style={{
                    flex: 1,
                    alignContent: "center",
                    justifyContent: "center",
                }}
            >
                <ActivityIndicator
                    style={{ alignSelf: "center" }}
                    size="large"
                />
            </View>
        );
    } else {
        return (
            <View style={styles.container}>
                <View style={styles.body}>
                    <Modal animated visible={visible} animationType="fade">
                        <ActionOptions
                            onReselect={onReselect}
                            onClose={onClose}
                            unsatisfied={unsatisfied}
                            events={props.allEvents}
                            genres={props.genres}
                            newTimeChange={newTimeChange}
                            filters={props.filters}
                        />
                    </Modal>
                    <Timeline
                        onEventPress={(event) => onEventPress(event)}
                        data={events}
                        timeStyle={{
                            textAlign: "center",
                            backgroundColor: "#cc5327",
                            color: "white",
                            padding: 5,
                            borderRadius: 13,
                        }}
                      />
                </View>
                <View style={styles.footer}>{renderProceedButton()}</View>
            </View>
        );
    }
};

I believe the issue here is that the loop that iterates over allRoutes needs to be wrapped in a Promise.all . Right now, in each iteration of the loop, the rest of the code in the try block will wait for the data to be fetched, but setLoading is called outside of that scope and is not being told to await anything else.

Instead of a for loop, maybe try having the iteration be something like

await Promise.all(allRoutes.map( (route) => {
  //code that's currently in the for loop
}))

Hope that makes sense. I found this post helpful on the subject matter as well: https://zellwk.com/blog/async-await-in-loops/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM