How to combine a loop of Axios calls with an await-ed function? - vue.js

That's a tricky one I guess. I'm prompting user for a word which I validate with an Axios API call. Once validation clears, the main loop of my game - hangman - starts with a wait between each move (hence the use of await).
Problem: in current version, the main game loop (starting after "once validation clears, game starts below" comment) must starts after validation, when in fact it starts at the same time which messes everything up.
And I can't put my main loop inside the then() part of my Axios call because in that case the await-ed function call ceases to work.
Any idea to get out of this mess?
async startGameComputerGuesser () {
var wordIsValidated = false
const vm = this
const dispatcher = {
execute: function () {
const wordApiBaseUrl = 'https://www.dictionaryapi.com/api/v1/references/sd4/xml'
wordToGuess = prompt('Enter a word:').toLowerCase()
const dispatcher = this
vm.axios.get(`${wordApiBaseUrl}/${wordToGuess}?key=${wordApiKey}`).then(res => {
if (!res.data.includes('def')) {
dispatcher.execute()
} else {
wordIsValidated = true
}
})
}
}
dispatcher.execute()
// once validation clears, game starts below
if (wordIsValidated) {
while (!this.$store.state.gameIsOver) {
await this.resolveAfter2Seconds()
// main loop of the game goes here
}
}
}

use await inside the execute and return true/false then use while to check that condition like below
async startGameComputerGuesser() {
let wordIsValidated = false;
const vm = this;
const dispatcher = {
async execute() {
const wordApiBaseUrl = 'https://www.dictionaryapi.com/api/v1/references/sd4/xml'
const wordToGuess = prompt('Enter a word:').toLowerCase();
const res = await vm.axios.get(`${wordApiBaseUrl}/${wordToGuess}?key=${wordApiKey}`);
return res.data.includes('def');
}
}
// validation
while (!wordIsValidated) {
wordIsValidated = await dispatcher.execute();
}
// game starts below
while (!this.$store.state.gameIsOver) {
await this.resolveAfter2Seconds()
// main loop of the game goes here
}
}
Example code:
const startGameComputerGuesser = async function() {
let wordIsValidated = false;
const dispatcher = {
async execute() {
const res = await new Promise(res => setTimeout(() => res(Math.floor(Math.random() * 10)), 500));
console.log(res);
return res == 6;
}
}
// validation
while (!wordIsValidated) {
wordIsValidated = await dispatcher.execute();
}
// once validation clears, game starts below
console.log('started');
}
startGameComputerGuesser();

Related

How to make pagination work? Async await function in vue.js 3 setup

I was trying to make an app which lists a user's repositories from github using github API, however I'm having a big problem with fetching data from all pages (so far I can only get repos from one page). I tried to fix it by using an async/await function (instead of Promise), but it's also my first time using vue3 and I have no idea how to have a function inside of the setup() method.
The current code is here:
https://github.com/agzpie/user_repos
My try at using async/await, which didn't work:
import ListElement from "./components/ListElement";
import { ref, reactive, toRefs, watchEffect, computed } from "vue";
export default {
name: "App",
components: {
ListElement,
},
setup() {
const name = ref(null);
const userName = ref(null);
const state = reactive({ data: [] });
let success = ref(null);
const userNameValidator = /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i;
const split1 = reactive({ spl1: [] });
const split2 = reactive({ spl2: [] });
async function myFetch() {};
/*
* Check for input in the form and then fetch data
*/
watchEffect(() => {
if (!userName.value) return;
if (!userNameValidator.test(userName.value)) {
console.log("Username has invalid characters");
return;
}
let hasNext = false;
state.data = [];
do {
async function myFetch() {
let url = `https://api.github.com/users/${userName.value}/repos?per_page=5`;
let response = await fetch(url);
if (!response.ok) {
success.value = false;
throw new Error(`HTTP error! status: ${response.status}`);
}
success.value = true;
// check response.headers for Link to get next page url
split1.spl1 = response.headers.get("Link").split(",");
let j = 0;
while (j < split1.spl1.length) {
split2.spl2[j] = split1.spl1[j].split(";");
console.log(split2.spl2[j][0]);
console.log(split2.spl2[j][1]);
if (split2.spl2[j][1].includes("next")) {
let urlNext = split2.spl2[j][0].replace(/[<>(\s)*]/g, "");
console.log(urlNext);
url = urlNext;
hasNext = true;
break;
} else {
hasNext = false;
}
j++;
}
// second .then
let myData = await response.json();
state.data.push(...myData);
console.log("data", myData);
name.value = "";
}
myFetch().catch((err) => {
if (err.status == 404) {
console.log("User not found");
} else {
console.log(err.message);
console.log("oh no (internet probably)!");
}
});
} while (hasNext);
});
// Sort list by star count
const orderedList = computed(() => {
if (state.data == 0) {
return [];
}
return [...state.data].sort((a, b) => {
return a.stargazers_count < b.stargazers_count ? 1 : -1;
});
});
return {
myFetch,
success,
isActive: true,
name,
userName,
ListElement,
...toRefs(state),
orderedList,
};
},
};
Any help would be highly appreciated
The call to myFetch() near the end is a call to an async function without an await, so it is effectively going to loop (if hasNext was initialized to true, but it isn't) without waiting for it to complete.
You should probably change that line to await myFetch() and wrap it all with a try/catch block.
I also don't really care for the way you're directly updating state inside the async myFetch call (it could also be doing several of those if it looped) and perhaps it should be returning the data from myFetch instead, and then you can use let result = await myFetch() and then make use of that when it returns.
Also, instead of awaiting myFetch() result, you could not await it but push it onto a requests array and then use await Promise.all(requests) outside the loop and it is one operation to await, all requests running in parallel. In fact, it should probably be await Promise.allSettled(requests) in case one of them fails. See allSettled for more.
But also I wonder why you're reading it paged if the goal is to fetch them all anyway? To reduce load on the server? If that is true, issuing them paged but in parallel would probably increase the load since it will still read and return all the data but require multiple calls.

why does async inside computed make infinity loops (vue)?

My computed of component like this :
export default {
computed: {
dataDoctorPerPage: async function () {
const start = this.pagination.page * this.pagination.rowsPerPage - this.pagination.rowsPerPage;
const end = start + this.pagination.rowsPerPage - 1;
const doctors = this.dataDoctor
const newDoctors = {}
let key = 0
for(let item in doctors) {
if(key >= start && key <= end) {
for (let i = 0; i < doctors[item].length; i++) {
const params = {
hospitalId: doctors[item][i].hospital_id,
doctorId: doctors[item][i].doctor_id,
}
await this.getDataSchedule(params) /* call async */
// console.log(this.dataSchedule)
}
newDoctors[item] = doctors[item]
}
key++
}
return newDoctors
}
}
}
If the dataDoctorPerPage called it will run the script
await this.getDataSchedule(params) will call async/api by vuex store. My problem is there. when I call await this.getDataSchedule(params), it will loop without stopping
My vuex store like this :
const actions = {
async getDataSchedule ({ commit }, payload) {
const result = await api.getDataSchedule(payload)
const items = result.data
commit('setDataSchedule', { items: items })
},
}
How can I solve this problem?
Whether there can not run async in computed?
Computed should not use async. If you want to do that, you need another library for it. https://alligator.io/vuejs/async-computed-properties/
But what you want to do is use an asynchronous method (in the component or store) and set the data somewhere (in the component's data or store's state), then have your computed value reference that.

How to exit a while loop depending of the result of an Axios call?

In order to make sure a user-entered word exists, I need to keep prompting (while loop) for a word until the word API finds the word.
My question is: how can I exit the while loop depending of Axios call result?
Below is my code so far.
const wordApiBaseUrl = 'https://www.dictionaryapi.com/api/v1/references/sd4/xml'
while (true) {
const wordToGuess = prompt('Enter a word:').toLowerCase()
const endPointUrl = `${wordApiBaseUrl}/${wordToGuess}?key=${wordApiKey}`
this.axios.get(endPointUrl).then(res => {
if (res.data.includes('def')) {
break
}
})
}
Try this:
const wordApiBaseUrl = 'https://www.dictionaryapi.com/api/v1/references/sd4/xml'
const vm = this; // <--- assuming this is the Vue instance
const dispatcher = {
execute: function() {
const wordToGuess = prompt('Enter a word:').toLowerCase()
const endPointUrl = `${wordApiBaseUrl}/${wordToGuess}?key=${wordApiKey}`
const dispatcher = this;
vm.axios.get(endPointUrl).then(res => {
if (!res.data.includes('def')) {
dispatcher.execute();
}
})
}
}
dispatcher.execute();
Rather than using a while loop or using an async/await you can use recursion in our promise. If the result is not satisfied, re-run the AJAX call.

redux-saga: eventChannel and listener which react callback return

In react-native backhandler listener react to callback function and act appropriately.
I need to read my store and depending on it, return true or false.
But I cant use select effect in normal function and I cant affect listener callback function from "watchBackButton" function.
export function* backButtonListen() {
return eventChannel(emitter => {
const backHandlerListener = BackHandler.addEventListener(
"hardwareBackPress",
() => {
emitter("back pressed");
}
);
return () => {
backHandlerListener.remove();
};
});
}
export function* watchBackButton() {
const chan = yield call(backButtonListen);
try {
while (true) {
let back = yield take(chan);
}
}
Since event channels are not bidirectional, I don't think there is a way to get some current state from saga to event channel using the select effect.
However, it is possible to access the store directly. There are multiple ways to get the store instance to the event channel. See my other answer here.
Using e.g. the context method you could do something like this:
// redux.js
...
const store = createStore(...);
sagaMiddleware.runSaga(rootSaga, {store});
// root-saga.js
export default function * rootSaga(context) {
yield setContext(context);
yield fork(watchBackButton);
}
// watch-back-button.js
export function* backButtonListen() {
const store = yield getContext('store');
return eventChannel(emitter => {
const backHandlerListener = BackHandler.addEventListener(
"hardwareBackPress",
() => {
emitter("back pressed");
return store.getState().foo === 'bar';
}
);
return () => {
backHandlerListener.remove();
};
});
}
export function* watchBackButton() {
const chan = yield call(backButtonListen);
try {
while (true) {
let back = yield take(chan);
}
}

Unhandled Promise Rejection while using object response from React Native Fetch

I'm currently building a Hacker News Reading Client in React Native using their Official API
I want to display the top stories of the day. So first I get the id's of the top posts for the day and then use those id's to fetch the stories.
But when I run this the promise gets rejected with ids.map is undefined.
How can I fix this ?
FeedScreen.js
import { getTopStoriesID, getStoriesByID } from '../../hacknews-api';
class FeedScreen extends Component {
constructor(props) {
super(props);
this.state = {
loaded: false,
stories: [],
};
}
async componentDidMount() {
const storiesID = await getTopStoriesID();
const stories = await getStoriesByID(storiesID[10]);
this.setState({ stories });
}
render() {
return <Text>{this.state.stories}</Text>;
}
}
export default FeedScreen;
hacknews-api.js
const BASE_URL = 'https://hacker-news.firebaseio.com/v0';
export const getTopStoriesID = async () => {
const res = await fetch(BASE_URL + '/topstories.json');
const storiesID = await res.json();
return storiesID;
};
export const getStoriesByID = async (ids) => {
const res = await Promise.all(ids.map(async (id) => {
const res = await fetch(BASE_URL + `/item/{id}.json`)
const story = await res.json();
return story;
}));
const stories = await res.json();
return stories;
}
You can use Array.prototype.map() on an array and not an object.
The map() method creates a new array with the results of calling a
provided function on every element in the calling array.
Example
export const getStoriesByID = async (ids) => {
// check if the argument is defined and its an Array
if(ids && ids.constructor === Array) {
// do something with array of ids, for example do a map()
} else {
// do something otherwise
}
}