Dynamic request with node-fetch - javascript

I need to obtain a value from an API which is actualized quickly. The problem is that the code I wrote bring me always the same value. I tried two ways:
var fetch = require("node-fetch");
for(let i=0; i<5; i++){
setTimeout(function(){}, 3000);
fetch('https://api.binance.com/api/v3/avgPrice?symbol=ETHBTC')
.then(res => res.json())
.then(data => {console.log(data.price)});
}
and in a synchronous fashion:
var fetch = require("node-fetch");
var request = async () => {
var response = await fetch('https://api.binance.com/api/v3/avgPrice?
symbol=ETHBTC');
var json = await response.json();
console.log(json.price);
}
for(let i=0; i<5; i++) {
setTimeout(function(){request();}, 3000);
}
but I always obtain something like this:
0.03244206
0.03244206
0.03244206
0.03244206
0.03244206
Any suggestion?

You need to understand that setTimeout doesnt stop the execution of the loop... The loop sets all timeouts to be executed right one after another
const fetch = require("node-fetch");
let i = 0, times = 5;
const interval = setInterval(() => {
if (++i < times) clearInterval(interval);
fetch('https://api.binance.com/api/v3/avgPricesymbol=ETHBTC')
.then(res => res.json())
.then(data => {console.log(data.price)});
},3000);
Using async/await:
const fetch = require("node-fetch");
let i = 0, times = 5;
const interval = setInterval(async () => {
if (++i < times) clearInterval(interval);
const response = await fetch("https://api.binance.com/api/v3/avgPricesymbol=ETHBTC");
const json = await response.json();
console.log(json);
},3000);

Related

iterating Javascript array and delete based on condition

I want to iterate through an array of words, look up the definition and delete the word if no definition is found.
my code looks as follows;
var words = ["word1", "word2", "word3",]
function Meaning(words){
const getMeaning = async () => {
const response = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${words}`)
const myJson = await response.json()
for(i = 0; i < words.length; ++i) {
if(!response[i]){
myJson.splice(i,1)
console.log(myJson)
}
}}
This is not really doing anything atm. Where am I going wrong?
edit to add context
tried like this as well;
for(i = 0; i < words.length; ++i)
fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${words[i]}`).then((response) => {
if (response === 404) {
let response = words
words[i].splice(i,1)
console.log(response)
}
throw new Error('Something went wrong');
})
.then((responseJson) => {
let response = words
response[i].splice(i,1)
})
.catch((error) => {
console.log(error)
});
I can print out the 404 error when it finds no definition, but I can't remove it from the words array
After quick look at the API, and it appears to handle only single words, so the caller needs to make the requests one at a time. Here's how to do it...
const baseUrl = 'https://api.dictionaryapi.dev/api/v2/entries/en/';
// one word lookup. resolve to an array of definitions
async function lookupWord(word) {
const res = await fetch(baseUrl + word);
return res.json();
}
// resolve to a bool, true if the word is in the corpus
async function spellCheck(word) {
const defArray = await lookupWord(word);
return Array.isArray(defArray) && defArray.length > 0;
}
// create a spellCheck promise for every word and resolve with the results
// note, this mutates the array and resolves to undefined
async function spellCheckWords(array) {
const checks = await Promise.all(array.map(spellCheck));
for (let i=array.length-1; i>=0; i--) {
if (!checks[i]) array.splice(i,1);
}
}
// test it (a little)
let array = ['hello', 'whereforeartthou', 'coffee'];
spellCheckWords(array).then(() => {
console.log(array)
})
try this code, you need to check every single element of array from response
var words = ["word1", "word2", "word3"];
function Meaning(words) {
const getMeaning = async () => {
const response = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${words}`)
const myJson = await response.json()
let result = [];
myJson.forEach(element => {
if(words.includes(element)) {
result.push(element)
}
});
return result;
}
return getMeaning();
}

execute axios request multiple times only get one output

i have the following code:
for (var i = 0; i < subscriptions.length; i++) {
axios.get(`SOME_URL/${subscriptions[i]}`, config1)
.then((result) => {
return result.data.subscriptionId
})
.then((result) => {
axios.get(`SOME_URL/${result}/devices`, config2)
.then((data) => {
activationCodeAndDevice[data.data.devices[0].id] = result
return activationCodeAndDevice
})
.then((result) => {
console.log(result);
})
.catch((err) => {
console.log(err);
})
})
.catch((err) => {
console.log(err);
})
}
Now the console.log(result) will print as many times as the for loop goes through. What is your suggestion to only print the result one time when all the Axios requests are done executing?
Now i did build something like this but with 3 different requests:
var request1 = axios.get('request1')
var request2 = axios.get('request2')
var request3 = axios.get('request3')
Promise.allSettled([request1, request2, request3])
.then((values) => {
//do something
})
.catch((err) => {
//do something
})
I have no idea on how to get for a request that I have to perform multiple times, depending on an array of values, but only get one time the output. Of course, i could write it in a file and just have a look at the file once the data is written, but i want it to have it on the console.
thanks
Do something like:
const promises = subscriptions.map(subscription => axios.get(`SOME_URL/${subscription}`, config1))
Promise.allSettled(promises).then((values) => {
...
})
The map converts the requests to an array of promises, and the Promise.allSettled waits for the array to succeed.
If you really insist in using a for loop you can also do the following:
let promises = [];
for(let i = 0; i < subscriptions.length; i++) {
promises.push(axios.get(`SOME_URL/${subscription}`, config1));
}
Promise.allSettled(promises).then((values) => {
...
})
I managed to solve it like this:
const getSubscriptionResponse = subscriptions.map(subscription => axios.get(`SOME_URL/${subscription}`, config1))
Promise.allSettled(getSubscriptionResponse).then((values) => {
var subscriptionsValue = []
for (let i = 0; i < values.length; i++) {
subscriptionsValue.push(values[i].value.data.subscriptionId);
}
const getDeviceResponse = subscriptionsValue.map(sub => axios.get(`SOME_URL/${sub}/devices`, config2))
Promise.allSettled(getDeviceResponse).then((res) => {
var finalResults = {}
for (let j = 0; j < res.length; j++) {
finalResults[res[j].value.data.devices[0].id] = subscriptionsValue[j]
}
console.log(finalResults);
})
})
this will only print once the finalResult
{
A:A,
B:B,
C:C,
.
.
.
}
it looks a bit messy with all the for loops but I actually need to get the data from the array of responses.

Javascript cancel async for loop

I found this code in a project:
const fn = async () => {
let x = 0;
for(let i = 0; i < 50; i++){
const res = await api.call(i);
if(res.someProp) x++;
}
return x;
}
I want to be able to stop it mid way, so that if I call it again, it will start from scratch and discard the previous call results. To avoid making two sets of requests at the same time.
This should do:
let token;
const fn = async () => {
const my = token = Symbol();
let x = 0;
for(let i = 0; i < 50 && my == token; i++){
const res = await api.call(i);
if(res.someProp) x++;
}
return x;
}
While there still can be some overlap between the calls, any previous loops will break their iteration as soon as the next fn() call is started.
You can use any technique of using an external flag variable to break the loop.
As a workaround you can try to use a custom Promise class (Live demo):
import CPromise from "c-promise2";
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
async function api(i) {
console.log(`Async API call [${i}]`);
await delay(100);
return {};
}
const fn = () =>
CPromise.from(function* () {
let x = 0;
for (let i = 0; i < 50; i++) {
const res = yield api.call(i);
if (res.someProp) x++;
}
return x;
});
const cancelablePromise = fn().then(
() => console.log("Done"),
(err) => console.log(`Fail: ${err}`) // Fail: CanceledError: canceled
);
setTimeout(() => {
cancelablePromise.cancel(); // abort the async sequence (loop) after 3500ms
}, 3500);

Looping through a list of fetch() calls

I'm trying to loop through a bunch of data and make asynch calls. However, I'm not getting the syntax right
async function getEmailData(conversationId){
fetch(aysynch)
.then(response => {return response.json(); })
.then(data => {
dictionary = {}
console.log(data)
var info = data.Body.ResponseMessages.Items[0].Conversation.ConversationNodes[0].Items[0]
console.log(info)
var conversationId = info.ConversationId.Id
var from = info.From.Mailbox.EmailAddress
var to = info.ToRecipients.map(function(recipient) {return recipient.EmailAddress})
var date = info.DateTimeReceived
dictionary[conversationId] = {'from':from, 'to': to, 'date': date}
return dictionary
})
}
x = [listOfIds] //10 in total
for (i=0; i<x.length; i++) {
console.log(x[i].ConversationId.Id)
let response = await getEmailData(x[i].ConversationId.Id)
let data = await response
console.log(data)
}
This is printing out all of the ID's and then grabbing the list id in x and running that one 10 times. How do I make the aysnch request for each request?
Some issues:
The function getEmailData is not returning anything. You need to return the result of the promise chain.
async has no use if you don't use await inside such a function
await outside an async function is invalid.
await response is not useful when response is already the result of an await
declare your variables (with let, var, const)
So do this:
function getEmailData(conversationId){
return fetch(aysynch)
.then(response => response.json())
.then(data => {
const dictionary = {};
console.log(data);
var info = data.Body.ResponseMessages.Items[0].Conversation.ConversationNodes[0].Items[0];
console.log(info);
var conversationId = info.ConversationId.Id;
var from = info.From.Mailbox.EmailAddress;
var to = info.ToRecipients.map(recipient => recipient.EmailAddress);
var date = info.DateTimeReceived;
dictionary[conversationId] = {from, to, date};
return dictionary;
});
}
(async function() {
let x = [1, 2, 4, 6, 9, 13, 23, 22, 24, 19]; //10 in total
for (let i=0; i<x.length; i++) {
console.log(x[i].ConversationId.Id);
let data = await getEmailData(x[i].ConversationId.Id);
console.log(data);
}
})(); // Immediately invoked
You should use let. You are are declaring a global variable i.
for (let i = 0; i < x.length; i++) {
console.log(x[i].ConversationId.Id)
let response = await getEmailData(x[i].ConversationId.Id)
let data = await response
console.log(data)
}

how to display responses from a looped scrape (cheerio)

I am scraping this site to collect all rows with the year 2013, but there are 7 pages and I have my request in a loop. How can I display the results after all 7 responses have been received? If I simply try to console.log the rowTrack array, it displays empty because of the async nature of the code. Ideally I want to run the requests in order of the loop so that the results of the first page are the first elements of the array etc..
var request = require("request"),
cheerio = require("cheerio"),
rowTrack = [];
for (var i = 1; i <= 7; i++) {
var url = "http://www.boxofficemojo.com/alltime/world/?pagenum=" + i + "&p=.htm";
request(url, function(error, response, body) {
if (!error) {
var $ = cheerio.load(body),
rows = $('table table tr');
rows.each(function(j, element) {
var select = $(element.children).text().split('\r\n')
select.shift();
select.pop();
if (select[select.length - 1] == "2013") {
rowTrack.push(select);
}
});
}
});}
How can I display the results?
The site you're scraping has changed a bit since the question was asked. The table is still there, but the URL and pagination are a bit different.
JS has moved on to promises and the requests package is deprecated. Nowadays, with promises, you'd do:
const cheerio = require("cheerio"); // ^1.0.0-rc.12
const baseUrl =
"https://www.boxofficemojo.com/chart/top_lifetime_gross/?area=XWW";
(async () => {
const results = [];
for (let i = 0; i < 6; i++) {
const response = await fetch(`${baseUrl}&offset=${i * 100}`);
const $ = cheerio.load(await response.text());
results.push(...[...$("tr")]
.map(e => [...$(e).find("td")].map(e => $(e).text()))
.filter(e => e.at(-1) === "2013")
);
}
console.log(results);
})();
The above code runs in series, but you can parallelize it with Promise.all:
const cheerio = require("cheerio");
const baseUrl =
"https://www.boxofficemojo.com/chart/top_lifetime_gross/?area=XWW";
(async () => {
const results = await Promise.all(
[...Array(6)].map(async (_, i) => {
const response = await fetch(`${baseUrl}&offset=${i * 100}`);
const $ = cheerio.load(await response.text());
return [...$("tr")]
.map(e => [...$(e).find("td")].map(e => $(e).text()))
.filter(e => e.at(-1) === "2013");
})
);
console.log(results.flat());
})();
Node 18 has native fetch, but if you're stuck with a legacy situation without promises, you can store each result in an array and use a counter to determine how many requests have completed. When the last request resolves, trigger the next stage of processing.
const cheerio = require("cheerio");
const request = require("request"); // ^2.88.2
const getRankings = done => {
const results = [];
const total = 6;
let completed = 0;
const baseUrl =
"https://www.boxofficemojo.com/chart/top_lifetime_gross/?area=XWW";
for (let i = 0; i < total; i++) {
request(`${baseUrl}&offset=${i * 100}`, function (error, response, body) {
if (error) {
console.error(err);
}
const $ = cheerio.load(body);
results[i] = [...$("tr")]
.map(e => [...$(e).find("td")].map(e => $(e).text()))
.filter(e => e.at(-1) === "2013");
if (++completed === total) {
done(results.flat());
}
});
}
};
getRankings(results => {
console.log(results);
});
The above code runs all of the requests in parallel. To do the requests sequentially, you can chain the callbacks:
const cheerio = require("cheerio");
const request = require("request");
const getRankings = (done, results=[], total=6, i=0) => {
const baseUrl =
"https://www.boxofficemojo.com/chart/top_lifetime_gross/?area=XWW";
request(`${baseUrl}&offset=${i * 100}`, (error, response, body) => {
if (error) {
console.error(err);
}
const $ = cheerio.load(body);
results[i] = [...$("tr")]
.map(e => [...$(e).find("td")].map(e => $(e).text()))
.filter(e => e.at(-1) === "2013");
if (i + 1 === total) {
done(results.flat());
}
else {
getRankings(done, results, total, i + 1);
}
});
}
getRankings(results => {
console.log(results);
});
Error handling on failed requests is left as an exercise. I haven't bothered adapting modern JS idioms like .at(-1), .flat() and so forth to work on older Node versions. Cheerio's .toArray() can be used instead of spreads, .at(-1) can be recreated with roughly const last = a => a[a.length-1]; and .flat() can be [].concat(...results).

Categories

Resources