Fetch() running out of order - javascript

I am trying to have an API pull a word and set it to state. Then a function will read that state and complete its designed purpose. However, the way I originally coded it called it out of order. The third code snippet allows the code to run successfully, but I am not sure why. Could someone explain what the difference is or why the original way did not work?
Below is the API function followed by the second function.
wordNikApi = () => {
fetch("http://api.wordnik.com:80/v4/words.json/randomWords?hasDictionaryDef=true&minCorpusCount=0&minLength=5&maxLength=15&limit=1&api_key=/* Removed */")
.then( res => res.json() )
.then( ( result ) => {
this.setState({
apiWord: result[0].word,
});
console.log("wordNikApi: ", this.state.apiWord);
})
.catch( ( error ) => {
console.log("API ERROR: ", error);
})
};
resetGame = () => {
console.log("resetGame");
this.wordNikApi();
this.setState({
word: [],
count: 0,
allAttempts: [],
letterIndex: [],
numberOfBadAttempts: 0,
remainingAttempts: 6,
repeat: false,
pageLock: false,
invalidKey: false,
}, () => {
console.log("resetGame: function 1");
console.log(this.state.apiWord);
let fullWord = "word";
let wordArray = fullWord.split("");
let wordLength = wordArray.length;
// Send wordObj to state with value and index
let wordObj = wordArray.map((value, index) => {
return {
found: false,
val: value,
id: index,
}
})
this.setState({
word: wordObj,
wordLength: wordLength,
remainingAttempts: 6,
});
});
};
Functioning code:
resetGame = () => {
console.log("resetGame");
// this.wordNikApi();
fetch("http://api.wordnik.com:80/v4/words.json/randomWords?hasDictionaryDef=true&minCorpusCount=0&minLength=5&maxLength=15&limit=1&api_key=/* Removed */")
.then( res => res.json() )
.then( ( result ) => {
this.setState({
apiWord: result[0].word,
}, ()=> {
let fullWord = this.state.apiWord;
let wordArray = fullWord.split("");
let wordLength = wordArray.length;
// Send wordObj to state with value and index
let wordObj = wordArray.map((value, index) => {
return {
found: false,
val: value,
id: index,
}
})
this.setState({
word: wordObj,
wordLength: wordLength,
remainingAttempts: 6,
count: 0,
allAttempts: [],
letterIndex: [],
numberOfBadAttempts: 0,
repeat: false,
pageLock: false,
invalidKey: false,
});
});
})
.catch( ( error ) => {
console.log("API ERROR: ", error);
})
};

What is causing you a problem is the JS asynchronism, when you call the wordNikApi function within the resetGame function, you must use the await keyword, so that in this way the changes to the wordNijApi function are generated first and then continue with the flow of work. Try modifying the resetGame function like this:
const resetGame = async()=>{
...
await this.wordNikApi()
...
}

Fetch is an asynchronous function, which means it will run along side your other code, calling this.wordNikApi() sets the fetch request going but doesn't stop you script from continuing.
In your new version you have the code inside the .then() function which is what is called when the fetch request has called for the data and returned so your code inside here is waiting for this.wordNikApi() to finish before running in the 3rd snippet.
Hope this helped clear up Async and Sync a bit more, however there are better documents out there to explain this.

I know this is an old post but thought others may can use this.
I implemented a queue that calls fetch on the first item in the queue, then uses fetch().then to pull the next item, post it, then recurs if the queue is not empty. Here's the code I used:
var clientDataQueue = [];
function queueClientData(theData) {
clientDataQueue.push(theData);
console.log("++clientDataQueue.length:", clientDataQueue.length)
if (clientDataQueue.length == 1){
postFromQueue();
}
}
function postFromQueue() {
console.log("--clientDataQueue.length:", clientDataQueue.length)
if (clientDataQueue.length > 0) {
postClientdata(clientDataQueue[0]).then( () => {
clientDataQueue.shift();
postFromQueue();
});
}
}
function postClientdata(theData) {
var htmlData = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(theData)
};
return fetch('/api/clientData', htmlData)
}

Related

How to get the value of a variable out of the scope of the arrow function which created it

So, i have this code:
client.on("messageCreate", async (msg) => {
if (msg.content.startsWith("-p")) {
if (!msg.member.voice?.channel) return msg.channel.send("Por favor...");
const connection = joinVoiceChannel({
channelId: msg.member.voice.channel.id,
guildId: msg.guild.id,
adapterCreator: msg.guild.voiceAdapterCreator,
});
google
.youtube("v3")
.search.list({
key: process.env.YOUTUBE_TOKEN,
part: "id",
q: msg.content.slice(3),
maxResults: 1,
})
.then((response) => {
const { data } = response;
let link = "https://www.youtube.com/watch?v=" + data.items.id.videoId;
});
let args = msg.content.slice(3);
let stream = await play.stream(args);
let resource = createAudioResource(stream.stream, {
inputType: stream.type,
});
let player = createAudioPlayer({
behaviors: {
noSubscriber: NoSubscriberBehavior.Play,
},
});
player.play(resource);
connection.subscribe(player);
}
});
And I would like to use the value of the variable "link", which is a string, as a parameter for the method play.stre(args)
I'm don't fully understand how to get that done
Also, if possible I would like to find a way to store that value too inside an array outside the whole:
*client.on('messageCreate' , async msg => {... ...})*
You most likely want to call play.stream from within the then callback of list:
google.youtube("v3")
.search.list({
key: process.env.YOUTUBE_TOKEN,
part: "id",
q: msg.content.slice(3),
maxResults: 1,
})
.then(async (response) => {
const { data } = response;
let link = "https://www.youtube.com/watch?v=" + data.items.id.videoId;
let stream = await play.stream(link);
});
Like this, you'll make sure that you actually have a value set for link when using it to call stream.

React Query v3 useInfiniteQuery returns isLoading, isFetching always true and isFetchingNextPage always false

I have implemented infinite scroll with useInfiniteQuery from react-query v3. But isLoading, isFetching is always true after first page query. And isFetchingNextPage is always false. After initial load first page, next page requests is made with fetchNextPage()
This my useContents hook.
const useContents = (params) => {
return useInfiniteQuery(
['contents', params.Section],
({ pageParam = 0 }) => fetchContents(pageParam, params), {
getNextPageParam: (lastPage, allPages) => {
if (lastPage.payload.size === PAGE_SIZE) return allPages.length;
return false;
},
refetchInterval: 60000,
staleTime: 60000,
}
);
};
And this is fetchContents
const fetchContents = (pageParam, params) => {
return axios
.get(`${apiDomain}/contents`, {
params: {
Section: params.Section,
ViewType: params.ViewType,
Count: pageParam*PAGE_SIZE,
PageSize: PAGE_SIZE,
...params.questionSectionParam,
},
...generateAxiosOptions('GET'),
})
.then((res) => {
return {
message: res.data.message,
payload: fromJS(res.data.payload),
result: res.data.result,
status: res.status,
pageParam,
};
});
};
I have spent hours but couldn't find the cause.
It turns out that it's a silly mistake. I added a scroll event listener to ref coming from props with an empty array. Below the wrong code and fixed by removing the empty array.
useEffect(() => {
!!contentListRef && contentListRef.addEventListener('scroll', infiniteScroller, false);
return () => {
lastScrollTop = 0;
contentListRef.removeEventListener('scroll', infiniteScroller, false);
};
}, []); // <-- This empty array is the reason for this bug

rxjs subscribing late results to empty stream

I have the following piece of code. As is, with a couple of lines commented out, it works as expected. I subscribe to a stream, do some processing and stream the data to the client. However, if I uncomment the comments, my stream is always empty, i.e. count in getEntryQueryStream is always 0. I suspect it has to do with the fact that I subscribe late to the stream and thus miss all the values.
// a wrapper of the mongodb driver => returns rxjs streams
import * as imongo from 'imongo';
import * as Rx from 'rx';
import * as _ from 'lodash';
import {elasticClient} from '../helpers/elasticClient';
const {ObjectId} = imongo;
function searchElastic({query, sort}, limit) {
const body = {
size: 1,
query,
_source: { excludes: ['logbookType', 'editable', 'availabilityTag'] },
sort
};
// keep the search results "scrollable" for 30 secs
const scroll = '30s';
let count = 0;
return Rx.Observable
.fromPromise(elasticClient.search({ index: 'data', body, scroll }))
.concatMap(({_scroll_id, hits: {hits}}) => {
const subject = new Rx.Subject();
// subject needs to be subscribed to before adding new values
// and therefore completing the stream => execute in next tick
setImmediate(() => {
if(hits.length) {
// initial data
subject.onNext(hits[0]._source);
// code that breaks
//if(limit && ++count === limit) {
//subject.onCompleted();
//return;
//}
const handleDoc = (err, res) => {
if(err) {
subject.onError(err);
return;
}
const {_scroll_id, hits: {hits}} = res;
if(!hits.length) {
subject.onCompleted();
} else {
subject.onNext(hits[0]._source);
// code that breaks
//if(limit && ++count === limit) {
//subject.onCompleted();
//return;
//}
setImmediate(() =>
elasticClient.scroll({scroll, scrollId: _scroll_id},
handleDoc));
}
};
setImmediate(() =>
elasticClient.scroll({scroll, scrollId: _scroll_id},
handleDoc));
} else {
subject.onCompleted();
}
});
return subject.asObservable();
});
}
function getElasticQuery(searchString, filter) {
const query = _.cloneDeep(filter);
query.query.filtered.filter.bool.must.push({
query: {
query_string: {
query: searchString
}
}
});
return _.extend({}, query);
}
function fetchAncestors(ancestorIds, ancestors, format) {
return imongo.find('session', 'sparse_data', {
query: { _id: { $in: ancestorIds.map(x => ObjectId(x)) } },
fields: { name: 1, type: 1 }
})
.map(entry => {
entry.id = entry._id.toString();
delete entry._id;
return entry;
})
// we don't care about the results
// but have to wait for stream to finish
.defaultIfEmpty()
.last();
}
function getEntryQueryStream(entriesQuery, query, limit) {
const {parentSearchFilter, filter, format} = query;
return searchElastic(entriesQuery, limit)
.concatMap(entry => {
const ancestors = entry.ancestors || [];
// if no parents => doesn't match
if(!ancestors.length) {
return Rx.Observable.empty();
}
const parentsQuery = getElasticQuery(parentSearchFilter, filter);
parentsQuery.query.filtered.filter.bool.must.push({
terms: {
id: ancestors
}
});
// fetch parent entries
return searchElastic(parentsQuery)
.count()
.concatMap(count => {
// no parents match query
if(!count) {
return Rx.Observable.empty();
}
// fetch all other ancestors that weren't part of the query results
// and are still a string (id)
const restAncestorsToFetch = ancestors.filter(x => _.isString(x));
return fetchAncestors(restAncestorsToFetch, ancestors, format)
.concatMap(() => Rx.Observable.just(entry));
});
});
}
function executeQuery(query, res) {
try {
const stream = getEntryQueryStream(query);
// stream is passed on to another function here where we subscribe to it like:
// stream
// .map(x => whatever(x))
// .subscribe(
// x => res.write(x),
// err => console.error(err),
// () => res.end());
} catch(e) {
logger.error(e);
res.status(500).json(e);
}
}
I don't understand why those few lines of code break everything or how I could fix it.
Your use case is quite complex, you can start off with building up searchElastic method like the pattern bellow.
convert elasticClient.scroll to an observable first
setup the init data for elasticClient..search()
when search is resolved then you should get your scrollid
expand() operator let you recursively execute elasticClientScroll observable
use map to select data you want to return
takeWhile to decide when to complete this stream
The correct result will be once you do searchElastic().subscribe() the stream will emit continuously until there's no more data to fetch.
Hope this structure is correct and can get you started.
function searchElastic({ query, sort }, limit) {
const elasticClientScroll = Observable.fromCallback(elasticClient.scroll)
let obj = {
body: {
size: 1,
query,
_source: { excludes: ['logbookType', 'editable', 'availabilityTag'] },
sort
},
scroll: '30s'
}
return Observable.fromPromise(elasticClient.search({ index: 'data', obj.body, obj.scroll }))
.expand(({ _scroll_id, hits: { hits } }) => {
// guess there are more logic here .....
// to update the scroll id or something
return elasticClientScroll({ scroll: obj.scroll, scrollId: _scroll_id }).map(()=>
//.. select the res you want to return
)
}).takeWhile(res => res.hits.length)
}

Handling multiple ajax requests, only do the last request

I'm doing a project that fetch different types of data from SWAPI API (people, planets, etc.) using react but I have an issue with multiple Ajax request.
The problem is when I quickly request from 2 different URL for example, 'species' and 'people', and my last request is 'species' but the load time of 'people' is longer, I will get 'people' instead.
What I want is to get the data of the last clicked request, if that make sense.
How do I achieve that? All the solution I found from Google is using jQuery.
Here's a slice of my code in src/app.js (root element) :
constructor(){
super();
this.state = {
searchfield: '',
data: [],
active: 'people'
}
}
componentDidMount() {
this.getData();
}
componentDidUpdate(prevProps, prevState) {
if(this.state.active !== prevState.active) {
this.getData();
}
}
getData = async function() {
console.log(this.state.active);
this.setState({ data: [] });
let resp = await fetch(`https://swapi.co/api/${this.state.active}/`);
let data = await resp.json();
let results = data.results;
if(data.next !== null) {
do {
let nextResp = await fetch(data.next);
data = await nextResp.json();
let nextResults = data.results
results.push(nextResults);
results = results.reduce(function (a, b) { return a.concat(b) }, []);
} while (data.next);
}
this.setState({ data: results});
}
categoryChange = (e) => {
this.setState({ active: e.target.getAttribute('data-category') });
}
render() {
return (
<Header searchChange={this.searchChange} categoryChange={this.categoryChange}/>
);
}
I made a gif of the problem here.
Sorry for the bad formatting, I'm writing this on my phone.
You have to store your requests somewhere and to abandon old ones by making only one request active. Something like:
getData = async function() {
console.log(this.state.active);
this.setState({ data: [] });
// my code starts here
if (this.controller) { controller.abort() }
this.controller = new AbortController();
var signal = controller.signal;
let resp = await fetch(`https://swapi.co/api/${this.state.active}/`, { signal });
let data = await resp.json();
let results = data.results;
if(data.next !== null) {
do {
let nextResp = await fetch(data.next);
data = await nextResp.json();
let nextResults = data.results
results.push(nextResults);
results = results.reduce(function (a, b) { return a.concat(b) }, []);
} while (data.next);
}
this.setState({ data: results});
}

NodeJS returned data sometimes changes

I'm new to NodeJS and I have a problem I don't understand.
In this function, I call several API one after another to retrieve some data about a movie. The result isn't always the same. Most of the time, the result is correct, but sometimes the result isn't complete.
I tried using then to try and chain the API calls but it doesn't seem to work.
Any idea why the result isn't always the same? Any help would be appreciated.
// test fetchData(456165)
function fetchData(filmid) {
let average = array => array.reduce((a, b) => a + b) / array.length
var notes = []
mdb.movieInfo({
id: filmid,
language: 'fr'
},
(err, resOmdb) => {
notes.push(parseFloat(resOmdb.vote_average))
imdb
.getById(resOmdb.imdb_id, {
apiKey: 'e9d59b68',
timeout: 3000
})
.then(
allocine.api(
'search', {
q: `${resOmdb.title}`,
filter: 'movie'
},
function(error, resAllo) {
if (error) {
return
}
allocine.api(
'movie', {
code: `${resAllo.feed.movie[0].code}`
},
function(error, result) {
if (error) {
return
}
notes.push(parseFloat(result.movie.statistics.userRating) * 2)
}
)
// doesn't seem to execute all the time
allocine.api(
'showtimelist', {
zip: 44260,
movie: resAllo.feed.movie[0].code
},
function(error, resultCin) {
if (error) {
return
}
// sometimes doesn't appear in the result
resOmdb.cinemas = resultCin
}
)
}
)
)
.then(
function(result) {
notes.push(parseFloat(result.rating))
resOmdb.vote_average = average(notes).toFixed(2)
// check the result
console.log(util.inspect(resOmdb, false, null))
},
function(error) {
return
}
)
}
)
}
First of all you should decide if you want to use Promises or not.
If you do, promisify all functions. Next thing you need to do is 'return' your promises if they are used inside a function.
In your case your first imbd api call is not returned probably.
As next thing you should check if your node version supports async await.
Then you can easily do your api calls without any distractions.
'use strict';
const Promise = require('bluebird');
const mdb = Promise.promisfyAll(require('mdb'));
const allocine = Promise.pomisifyAll(require('allocine-api'));
// test fetchData(456165)
async function fetchDate(filmId) {
const notes = [];
const resOmdb = await mdb.movieInfoAsync({ id: filmId });
notes.push(parseFloat(resOmdb.vote_average));
const imdbResult = await imdb.getByIdAsync(resOmdb.imdb_id, { apiKey: 'e9d59b68', timeout: 3000 });
const resAllo = await allocine.apiAsync('search', { q: `${resOmdb.title}`, filter: 'movie' });
// and so on ...
}
UPDATE:
To speed up your function you can do requests concurrently.
To do so, use Promise.join
const [imdbResult, allocineResult] = await Promise.join(
imdb.getByIdAsync(resOmdb.imdb_id, { apiKey: 'e9d59b68', timeout: 3000 }),
allocine.apiAsync('search', { q: `${resOmdb.title}`, filter: 'movie' });
);

Categories

Resources