as I'm trying to learn how to use generator functions, I've run into a problem using it to run multiple fetchs.
What I'm trying to do:
I'm trying to use the Hackernews API to fetch a newsstory, and then afterwards, fetching a comment from the same API, using the id given in the response.
Output:
{ value: Promise { }, done: false }
TypeError: Cannot read property 'kids' of undefined
at getFirstComment (C:\Users\madsn\Documents\dev\generatorfunctions\fetchingInterTwice.js:13:29)
at getFirstComment.next ()
at runThroughIt (C:\Users\madsn\Documents\dev\generatorfunctions\fetchingInterTwice.js:31:35)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
{ value: undefined, done: true }
{ value: undefined, done: true }
Code:
const fetch = require("node-fetch")
function requestStuff(url) { ///simple request
return fetch(url).then(respJson => respJson.json()).catch(err => console.log(err));
}
function* getFirstComment() {
try {
//gets the story and the comments id(kids of type array)
var result1 = yield requestStuff("https://hacker-news.firebaseio.com/v0/item/8863.json?print=pretty")
var resID = result1.kids[0];
//gets the comment-text given the comment-id
var result2 = yield requestStuff("https://hacker-news.firebaseio.com/v0/item/" + resID + ".json?print=pretty")
var resText = result2.text
console.log(resText.text)
} catch (error) {
console.log(error)
}
}
let main = getFirstComment()
async function runThroughIt(generator) {
let stepOne = await generator.next()
console.log(stepOne)
let stepTwo = await generator.next()
console.log(stepTwo)
let stepThree = await generator.next()
console.log(stepThree)
}
runThroughIt(main)
Thanks in advance if you have a fix or the link to an article explaining this!
Here are the issues with your code:
Your requestStuff function should be async and the return value should be the evaluated result from the network request. Currently, you're simply returning the promise object.
The yield keyword will "kick" you out of the function BEFORE the result from the fetch expression is assigned to result1.
Here is how you could fix it:
const fetch = require("node-fetch")
async function requestStuff(url) { ///simple request
const res = await fetch(url)
.then(respJson => respJson.json())
.catch(err => console.log(err));
return res;
}
async function* getFirstComment() {
try {
//gets the story and the comments id(kids of type array)
var result1 = await requestStuff("https://hacker-news.firebaseio.com/v0/item/8863.json?print=pretty");
yield result1;
var resID = result1.kids[0];
//gets the comment-text given the comment-id
var result2 = await requestStuff("https://hacker-news.firebaseio.com/v0/item/" + resID + ".json?print=pretty")
var resText = result2.text
console.log(resText.text)
yield resText;
} catch (error) {
console.log(error)
}
}
let main = getFirstComment()
async function runThroughIt(generator) {
let stepOne = await generator.next();
console.log("res from step 1! ", stepOne);
let stepTwo = await generator.next()
console.log("res from step 2: ", stepTwo);
//will return undefined because the function has finished evaluating!
let stepThree = await generator.next()
console.log(stepThree)
}
runThroughIt(main)
The above works!
Now pay special attention to where the yield, await and async keywords are being called in my example and compare it with where you called it in your code!
Related
I'm studying the node.js module async,I want to find out if there is a way to change the async.retry method to retry even on successfull operations but stop based on some condition or response let's say its an api call.
According to its docs ,the function will continue trying the task on failures until it succeeds.if it succeeds it will only run only that time But how can i make it work the same on successfull operations and make it stop on some condition ?
const async = require('async');
const axios = require('axios');
const api = async () => {
const uri = 'https://jsonplaceholder.typicode.com/todos/1';
try {
const results = await axios.get(uri);
return results.data;
} catch (error) {
throw error;
}
};
const retryPolicy = async (apiMethod) => {
async.retry({ times: 3, interval: 200 }, apiMethod, function (err, result) {
// should retry untill the condition is met
if (result.data.userId == 5) {
// stop retring
}
});
};
retryPolicy(api);
Yes, You can just throw a custom error if condition is not met. Would be something like that:
const async = require('async');
const axios = require('axios');
const api = async () => {
const uri = 'https://jsonplaceholder.typicode.com/todos/1';
try {
const results = await axios.get(uri);
if(typeof result.data.userId != 'undefined' && result.data.userId == 5){ // change this condition to fit your needs
return results.data;
}else{
throw {name : "BadDataError", message : "I don't like the data I got"};
}
} catch (error) {
throw error;
}
};
I don't think this is possible.
On the async.retry documentation you can find this description:
Attempts to get a successful response from task no more than times
times before returning an error. If the task is successful, the
callback will be passed the result of the successful task. If all
attempts fail, the callback will be passed the error and result (if
any) of the final attempt.
However, using the delay function given here, you can do what you want another way:
const async = require('async');
const axios = require('axios');
const delay = (t, val) => {
return new Promise((resolve) => {
setTimeout(() => { resolve(val) }, t);
});
}
const api = async () => {
const uri = 'https://jsonplaceholder.typicode.com/todos/1';
try {
const results = await axios.get(uri);
return results.data;
} catch (error) {
throw error;
}
};
const retryPolicy = async (apiMethod) => {
const times = 3
const interval = 200
let data
for (count = 0; count < 3; count++) {
try {
data = await apiMethod()
catch(e) {
console.log(e)
await delay(interval)
continue
}
if (data.userId === 5) {
break;
}
await delay(interval)
}
// do something
};
retryPolicy(api);
const fs = require('fs')
const web3 = new Web3("ws://localhost:7545");
const contract_address = "0x7484d32e8911817702c5d7c764dBF7e592000b415";
async function web3Contract() {
const contract_abi = fs.readFileSync('./build/contracts/Bottle.json', 'utf8')
const abi = JSON.parse(contract_abi).abi;
// console.log(abi);
const Bottle = await new web3.eth.Contract(abi, contract_address);
const accounts = await web3.eth.getAccounts();
await Bottle.methods.setName("Palm").send({from:accounts[0]});
const greeting = await Bottle.methods.getGreeting().call();
console.log(greeting);
});
}
async function run() {
try {
await web3Contract();
} catch (err) {
console.log('Your error is this - ' , err);
} finally {
console.log('finally');
}
}
run();
getGreeting().call() is giving me this error. I tried numerous different ways and been stuck on here for hours. Not to sure what to do. https://gyazo.com/81970d03c1380cd513998f25deef9e40
You're incorrectly combining await and the callback function (in your case function(result) {}).
Since you're using await, it never reaches the callback function, and the returned Promise is resolved by the await statement.
In the context of your code, it makes more sense to return the value from the await and then print it.
// remove the callback function
const greeting = await Bottle.methods.getGreeting.call();
console.log(greeting);
I'm using async await inside of for loop as below.
for (let i = 0; i < result.length; i += 1) {
try{
const client = await axios.get(
`${process.env.user}/client/${result[i].id}`
);
} catch(error){
console.log(error)
}
if (client.data.success === true) {
result[i].Name = rider.data.client.Name;
result[i].PhoneNumber = rider.data.client.Number;
}
}
But I want to make this using 'new Promise' and 'promiss.all' to make it asynclously.
But I don'k know how to make this correctly doing error handle well.
Could you recommend some advice for this? Thank you for reading it.
This can be a basic solution, i think
let playList = []
for (let i = 0; i < result.length; i += 1) {
playList.push(axios.get(
`${process.env.user}/client/${result[i].id}`
).then(res => {
if (res.data.success === true) {
result[i].Name = rider.data.client.Name;
result[i].PhoneNumber = rider.data.client.Number;
}
}).catch(ex => console.log(ex)));
}
await Promise.all(playList)
This can also be done by using a foreach loop.
The for/foreach loop can be simplified by using a map function.
Js map function is equivalent of c# select linq function.
The fat arrow in js map function is not bound to return a value unlike c# select inner function which must return a value.
await Promise.all(result.map(async r => {
let client;
try {
client = await axios.get(`${process.env.user}/client/${r.id}`);
} catch (error) {
console.log(error)
}
if (client.data.success === true) {
r.Name = rider.data.client.Name;
r.PhoneNumber = rider.data.client.Number;
}
}));
Try this
var promises = result.map(r => axios.get(`${process.env.user}/client/${r.id}`);
Promise.all(promises).then(function(values) {
console.log('All promises done');
});
The idea is that if you are awaiting something, that is promise, you can await it, or call it to get promise
Example:
function Foo()
{
return new Promise(...); // Promise of int for example
}
you can do
var p = Foo(); //you will get promise
Or
var v = await Foo(); // you will get int value when promise resolved
This is how you do it with async/await + Promise.all:
const myResult = await Promise.all(result.map(({ id }) => {
return axios.get(`${process.env.user}/client/${id}`);
}));
// deal with the result of those requests
const parsed = myResult.map(data => /* your code here */);
Here is an example using Array.map to call your function along with Promise.all. I wrapped the axios request in a function so if one of your request fails, it wont stop every other requests. If you don't mind stopping when you got an issue, look at others answers to your question.
function fakeRequest() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: {
success: true,
client: {
Name: 'Todd',
Number: 5,
},
},
});
}, 300);
});
}
(async() => {
const result = [{}, {}, {}];
await Promise.all(result.map(async(x, xi) => {
try {
const client = await fakeRequest();
if (client.data.success === true) {
result[xi].Name = client.data.client.Name;
result[xi].PhoneNumber = client.data.client.Number;
}
} catch (err) {
console.log(err)
}
}));
console.log(result);
})();
At the moment I am using this code below to get the results of several Promises using async await:
let matchday = await createMatchday(2018, 21, [/*9 matches of matchday*/]);
//Further calculations
async function createMatchday(seasonNr, matchdayNr, matches) {
let md = new Matchday(seasonNr, matchdayNr, matches);
await md.getStandings(seasonNr, matchdayNr);
return md;
}
class Matchday {
constructor(seasonNr, matchdayNr, matches) {
this.seasonNr = seasonNr;
this.matchdayNr = matchdayNr;
this.matches = matches;
}
async getStandings(seasonNr, matchdayNr) {
let promiseArr = [];
promiseArr.push(makeHttpRequestTo(`http://externService.com/standings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`);
promiseArr.push(makeHttpRequestTo(`http://externService.com/homestandings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`));
promiseArr.push(makeHttpRequestTo(`http://externService.com/awaystandings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`));
promiseArr.push(makeHttpRequestTo(`http://externService.com/formstandings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`));
let resulArr = await Promise.all(promiseArr);
this.standings = resultArr[0];
this.homeStandings = resultArr[1];
this.awayStandings = resultArr[2];
this.formStandings = resultArr[3];
}
}
function makeHttpRequest(url) {
return new Promise((resolve, reject) => {
//AJAX httpRequest to url
resolve(httpRequest.responseText);
}
}
Is this actually the best way to read the values of several promises where the promises don't need to wait for each other to end but rather work at the same time by using Promise.all() or is there a better way to make e.g. several httpRequests at the same time because this seems quite repetetive?
Your URLs all follow the same sort of pattern, so you can greatly reduce your code by mapping an array of ['', 'home', 'away', 'form'] to the URLs. Then, map those URLs to Promises through makeHttpRequestTo, and then you can destructure the awaited results into the this. properties:
async getStandings(seasonNr, matchdayNr) {
const urls = ['', 'home', 'away', 'form']
.map(str => `http://externService.com/${str}standings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`);
const promiseArr = urls.map(makeHttpRequestTo);
[
this.standings,
this.homeStandings,
this.awayStandings,
this.formStandings
] = await Promise.all(promiseArr);
}
To populate each property individually rather than waiting for all responses to come back:
async getStandings(seasonNr, matchdayNr) {
['', 'home', 'away', 'form']
.forEach((str) => {
const url = `http://externService.com/${str}standings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`;
makeHttpRequestTo(url)
.then((resp) => {
this[str + 'Standings'] = resp;
});
});
}
If you don't want to wait for all requests to complete before continuing the execution flow, you could make the properties of the class be promises:
class Matchday {
constructor(seasonNr, matchdayNr, matches) {
this.seasonNr = seasonNr;
this.matchdayNr = matchdayNr;
this.matches = matches;
['standings', 'homeStandings', 'awayStandings', 'formStandings'].forEach(propertyName => {
let url = `http://externService.com/${propertyName.toLowerCase()}`
+ `?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`
this[propertyName] = makeHttpRequestTo(url)
});
}
}
Test using the following snippet
class Matchday {
constructor(seasonNr, matchdayNr, matches) {
this.seasonNr = seasonNr;
this.matchdayNr = matchdayNr;
this.matches = matches;
['standings', 'homeStandings', 'awayStandings', 'formStandings'].forEach(propertyName => {
let url = `http://externService.com/${propertyName.toLowerCase()}`
+ `?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`
this[propertyName] = makeHttpRequestTo(url)
});
}
}
/**************************************
* Test harness
**************************************/
function makeHttpRequestTo(url) {
// Fake an AJAX httpRequest to url
const requested_resource = url.match('^.*\/\/.*\/([^?]*)')[1];
const fake_response_data = 'data for ' + url.match('^.*\/\/.*\/(.*)$')[1];
let delay = 0;
let response = '';
switch (requested_resource) {
// To make it interesting, let's give the 'standings' resource
// a much faster response time
case 'standings':
delay = 250;
break;
case 'homestandings':
delay = 2000;
break;
case 'awaystandings':
delay = 3000;
break;
case 'formstandings':
delay = 4000; // <== Longest request is 4 seconds
break;
default:
throw (util.format('Unexpected requested_resource: %s', requested_resource));
}
return new Promise((resolve, reject) => {
setTimeout(() => resolve(fake_response_data), delay);
});
}
async function testAccessingAllProperties() {
const testId = "Test accessing all properties";
console.log('\n%s', testId);
console.time(testId)
let md = new Matchday(2018, 21, []);
console.log(await md.standings);
console.log(await md.homeStandings);
console.log(await md.awayStandings);
console.log(await md.formStandings);
console.timeEnd(testId)
}
async function testAccessingOnlyOneProperty() {
const testId = `Test accessing only one property`;
console.log('\n%s', testId);
console.time(testId)
let md = new Matchday(2018, 21, []);
console.log(await md.standings);
console.timeEnd(testId)
}
async function all_tests() {
await testAccessingAllProperties();
await testAccessingOnlyOneProperty();
}
all_tests();
Conclusion
The above snippet shows that the execution time is not penalized by properties that are not accessed. And execution time of accessing all properties is no worse than using promise.all.
You'd just need to remember to use await when accessing those properties.
To answer, No you shouldn't block other XHR or any I/O request which are not dependent on each other. I would have written your function like this;
const getFavourites = async () => {
try {
const result = await Promise.resolve("Pizza");
console.log("Favourite food: " + result);
} catch (error) {
console.log('error getting food');
}
try {
const result = await Promise.resolve("Monkey");
console.log("Favourite animal: " + result);
} catch (error) {
console.log('error getting animal');
}
try {
const result = await Promise.resolve("Green");
console.log("Favourite color: " + result);
} catch (error) {
console.log('error getting color');
}
try {
const result = await Promise.resolve("Water");
console.log("Favourite liquid: " + result);
} catch (error) {
console.log('error getting liquid');
}
}
getFavourites();
This way every async functions will be called at once, and no async action will block the other action.
To create a Promise you need to call new Promise((resolve, reject) => { return "Pizza"; })
You're doing it the right way
If you want you can shorten the code by using an array (and its functions like map, etc...), but it won't improve its performance
In node.js, I have a database transaction, where I want to call an async method in then callback, but I get error message the keyword 'await' is reserved.
This is async saveImage function:
const saveImage = async (parsedLink) => {
AWS.config.region = config.awsSettings.region;
AWS.config.accessKeyId = config.awsSettings.accessKeyId;
AWS.config.secretAccessKey = config.awsSettings.secretAccessKey;
const bucket = new AWS.S3({
params: {
Bucket: config.awsSettings.images_bucket_name,
},
});
const currentDateString = new Date().toISOString().replace(/\:|\./g, '-');
const bodystream = new Buffer(parsedLink.imgUrl, 'binary');
const imageUrlDomain = parseDomain(parsedLink.linkUrl).domain;
const params = {
Key: `${parsedLink.id}/${imageUrlDomain}_${currentDateString}${parsedLink.imgType}`,
ContentType: parsedLink.imageMime,
ContentEncoding: 'base64',
Body: bodystream,
};
const resultPromise = await bucket.upload(params).promise();
return resultPromise.Location;
};
If I want to use saveImage function, I get the error message.
module.exports.addTestObject = async (ctx) => {
const testObj = ctx.request.body;
try {
switch (testObj.type) {
case interestT.getType.link: {
const knexTestObject = TestObject.knex();
transaction(knexTestObject, trx =>
TestObject.query(trx)
.insert({
interestDate: testObj.date,
})
.then(newInterest => {
// save image
if (parsedLink.saveImg) {
parsedLink.imgUrl = await saveImage(testObj);
}
newInterest.$relatedQuery('linkInterestsRel', trx).insert({
linkHeader: testObj.linkHeader,
}),
}
),
)
.then((linkInterest) => {
console.log(linkInterest);
})
.catch((err) => {
throw err;
});
break;
}
default:
break;
}
ctx.response.body = interestObj;
} catch (err) {
const statusCode = err.status || 400;
ctx.throw(statusCode, err.message);
}
};
Regular functions run synchronously till they return. Therefore you cannot use await inside them as you cannot wait for an asynchronous event in a synchronous way.
JavaScript also has async functions, which look like regular functions, but are conceptually quite different: They run synchronously till they reach an await, then they stop and continue once the awaited Promise resolves. As such they cannot return their result synchronously, instead they return a Promise which then resolves when the function finished execution.
Therefore you need to convert your function into an async function:
async function getUsername() { // <-- async keyword here
return (await getUser()).name; // <-- await can be used inside
}
Now this does also work inside a .then callback:
getUser().then(async function(user) {
const friends = await getFriends(user);
// ...
})
But this somewhat mixes the abstraction async functions with their underlying primitive Promise. If you would just await the Promise instead of adding a .then callback, the code gets way more readable:
(async function() {
const user = await getUser();
const friends = await getFriends(user);
})();
The concrete question could be rewritten as:
const linkInterest = await transaction(knexTestObject, async trx => {
const newInterest = await TestObject.query(trx)
.insert({ interestDate: testObj.date, });
if (parsedLink.saveImg) {
parsedLink.imgUrl = await saveImage(testObj);
}
await newInterest.$relatedQuery('linkInterestsRel', trx)
.insert({ linkHeader: testObj.linkHeader, }),
});