Joining stores in IndexedDB with promises - javascript

Here's some code to join something from 3 object stores:
let db;
indexedDB.open('db', 1).onsuccess = ev => {
db = ev.target.result;
const tran = db.transaction(['s1', 's2', 's3']);
tran.objectStore('s1').get('third').onsuccess = ev1 =>
tran.objectStore('s2').index('connectTo').get('third').onsuccess = ev2 =>
tran.objectStore('s3').index('connectTo').get('third').onsuccess = ev3 => {
const [res1, res2, res3] = [ev1.target.result, ev2.target.result, ev3.target.result];
const result = {...res1, ...res2, ...res3};
......
}
}
Can I use promises or other means like async/await to avoid the heavy nesting? It'd be good if I can put these query processes in a function and get the result object as the return value.

Something like this should work.
const someFunction = async () => {
let db
const openDB = await indexedDB.open('db', 1)
db = openDB.target.result
const tran = db.transaction(['s1', 's2', 's3'])
const ev1 = await tran.objectStore('s1').get('third')
const ev2 = await tran.objectStore('s2').index('connectTo').get('third')
const ev3 = await tran.objectStore('s3').index('connectTo').get('third')
const [res1, res2, res3] = [
ev1.target.result,
ev2.target.result,
ev3.target.result,
]
const result = { ...res1, ...res2, ...res3 }
}
someFunction()
Personally I would store the results like this and eliminate the need for copies (if possible for you).
const result = { ...ev1.target.result, ...ev2.target.result, ...ev3.target.result }

Related

How can I simply request axios.all using loop with array of strings in React?

Please check my code first.
const arr = ['liver', 'heart', 'brain']
const url1 = `www.hospital.com/diseaseName?liver`
const url2 = `www.hospital.com/diseaseName?heart`
const url3 = `www.hospital.com/diseaseName?brain`
const request1 = axios.get(url1)
const request2 = axios.get(url2)
const request3 = axios.get(url3)
const fetchData = () => {
axios.all( [request1, request2, request3] )
.then(axios.spread((...response) => {
const responseOne = response[0].data
const responseTwo = response[1].data
const responseThree = response[2].data
})
}
What I'm trying to do is using the every element of arr, making each item's url, and request using axios.all
I think I can simplify the code using loop function such as array.map or other methods. I need some guidance. Thank you.
You can do something like this.
const URL = 'www.hospital.com/diseaseName';
const arr = ['liver', 'heart', 'brain'];
const requests = arr.map((a) => {
return axios.get(URL + '?' + a);
});
const fetchData = () => {
axios.all( requests )
.then(axios.spread((...response) => {
const responseOne = response[0].data
const responseTwo = response[1].data
const responseThree = response[2].data
})
}

How can I access functions output across Node.js?

I have 3 files:
Ingredients.js
const fs = require("fs");
const readline = require('readline');
const stream = require('stream');
const ingredients = () => {
const instream = fs.createReadStream('ingredients.txt');
const outstream = new stream;
const rl = readline.createInterface(instream, outstream);
const listIngredients = {};
rl.on('line', function (line) {
let lower = line.toLowerCase();
listIngredients[lower] = 0;
});
rl.on('close', function () {
console.log('listIngredients', listIngredients);
});
}
module.exports = ingredients;
cookbook.js:
let fs = require("fs");
const book = () => {
const regex = /\b(\w+)\b/g;
fs.readFile('cook-book.txt', 'utf8', function (err, data) {
let book = data;
let lower = book.toLowerCase();
let split = lower.match(regex);
console.log(split);
});
}
module.exports = book;
compare.js
const ingredients = require('./ingredients');
const book = require('./book');
I'm trying to increase the key values of ingredients every time they are mentioned in the cookbook. I think this should go into a different js file to make it cleaner.
Whilst i can console.log out the information from the above files, I cannot figure out how to actually access the data and make changes to the ingredients object in compare.js?
as others noticed your ingredients and book variables are functions having required information inside their scope and not returning it outside. to fix it, you have to return values.
as you're working with asynchronous stuff, your functions should be wrapped into Promise's to handle the flow correctly.
this code should help you:
const fs = require('fs');
const readline = require('readline');
const { Writable } = require('stream');
const fsp = fs.promises;
// ingredients.js
const getIngredients = async () => new Promise((resolve, reject) => {
const instream = fs.createReadStream('ingredients.txt');
const outstream = new Writable();
const rl = readline.createInterface(instream, outstream);
const listIngredients = {};
rl.on('line', line => {
const lower = line.toLowerCase();
listIngredients[lower] = 0;
});
rl.on('error', reject);
rl.on('close', () => resolve(listIngredients));
});
// cookbook.js
const getBookContent = async () => new Promise(async (resolve, reject) => {
try {
const wordRegEx = /\b(\w+)\b/g;
const book = await fsp.readFile('cook-book.txt', 'utf8')
const lower = book.toLowerCase();
return resolve(lower.match(wordRegEx));
} catch (error) {
return reject(error);
}
});
// compare.js
(async () => {
const ingredients = await getIngredients();
const words = await getBookContent();
console.log(ingredients);
console.log(words);
})();
the names of functions have been change for better representations of their instances.
i've also used an async iife to use async/await syntax, however you can still work with Promises themselves

How to optimally combine multiple axios responses

I am working with a React app. I have to create 2 objects using responses from 3 different APIs. For example:
DataObject1 will be created using API1 and API2
DataObject2 will be created using API1, API2, and API3
So, I am thinking about what would be the most optimal way of doing this by making sure 1 call each API only once.
I was thinking this:
const requestOne = axios.get(API1);
const requestTwo = axios.get(API2);
const requestThree = axios.get(API3);
axios.all([requestOne, requestTwo, requestThree]).then(axios.spread((...responses) => {
const dataObject1 = createDataObject1(responses[0], responses[1]);
const dataObject2 = createDataObject2(responses[0], responses[1], responses[2]);
// use/access the results
})).catch(errors => {
// react on errors.
})
const createDataObject1 = (response1, response2) => { //Combine required data and return dataObject1 }
const createDataObject2 = (response1, response2, response3) => { //Combine required data and return dataObject2 }
Is there a better way of doing this?
Looks fine.
You can change this
axios.all([requestOne, requestTwo, requestThree]).then(axios.spread((...responses) => {
const dataObject1 = createDataObject1(responses[0], responses[1]);
const dataObject2 = createDataObject2(responses[0], responses[1], responses[2]);
// use/access the results
})).catch(errors => {
// react on errors.
})
to
axios.all([requestOne, requestTwo, requestThree]).then((response) => {
const dataObject1 = createDataObject1(responses[0], responses[1]);
const dataObject2 = createDataObject2(responses[0], responses[1], responses[2]);
// use/access the results
}).catch(errors => {
// react on errors.
})
because it is unnecessary to spread and rest.
If you don't want to use them like responses[0], responses[1], etc then you can use:
axios.all([requestOne, requestTwo, requestThree]).then(axios.spread((response1, response2, response3) => {
const dataObject1 = createDataObject1(response1, response2);
const dataObject2 = createDataObject2(response1, response2,response3);
// use/access the results
})).catch(errors => {
// react on errors.
})
Are you using thunk middleware to make async calls in Redux? I don't want to assume that you are, but that seems like a good basic approach here.
const requestOne = axios.get(API1);
const requestTwo = axios.get(API2);
const requestThree = axios.get(API3);
Okay. So now requestOne.data has the result of making the axios get request. Or, would if the thunk creator was async and the code was const requestOne = await axios.get(API1);
Do you need to parse the data further from request___.data ?
If not you can just have
const dataObj1 = { response1: requestOne.data, response2: requestTwo.data }
const dataObj2 = { ... dataObject1, response3: requestThree.data };
Full answer:
// store/yourFile.js code
export const YourThunkCreator = () => async dispatch => {
try {
const const requestOne = await axios.get(API1);
// other axios requests
const dataObj1 = { response1: requestOne.data, response2: requestTwo.data }
const dataObj2 = { ... dataObject1, response3: requestThree.data };
// other code
dispatch(// to Redux Store);
} catch (error) {
console.error(error);
}

How to fetch data from multiple urls at once?

I have a function that fetches from a url in React
const DataContextProvider = (props) => {
const [isLoading, setLoading] = useState(false);
const [cocktails, setCocktails] = useState([]);
useEffect(() => {
const fetchCocktailList = async () => {
const baseUrl = 'https://www.thecocktaildb.com/api/json/v1/1/';
setLoading(true);
try {
const res = await fetch(`${baseUrl}search.php?s=margarita`);
const data = await res.json();
console.log(data);
setCocktails(data.drinks);
setLoading(false);
} catch (err) {
console.log('Error fetching data');
setLoading(false);
}
};
fetchCocktailList();
}, []);
How I'm mapping data so far.
const DrinkList = () => {
const { cocktails } = useContext(DataContext);
return (
<div className='drink-list-wrapper'>
{cocktails.length > 0 &&
cocktails.map((drink) => {
return <DrinkItem drink={drink} key={drink.idDrink} />;
})}
</div>
);
};
However I want to fetch from this url also ${baseUrl}search.php?s=martini
I would like a good clean way to do this and set my state to both of the returned data.
First base the data fetch function on a parameter:
const fetchCocktail = async (name) => {
const baseUrl = 'https://www.thecocktaildb.com/api/json/v1/1/';
try {
const res = await fetch(`${baseUrl}search.php?s=` + name);
const data = await res.json();
return data.drinks;
} catch (err) {
console.log('Error fetching data');
}
}
Then use Promise.all to await all results:
setLoading(true);
var promises = [
fetchCocktail(`margarita`),
fetchCocktail(`martini`)
];
var results = await Promise.all(promises);
setLoading(false);
DrinkList(results);
Where results will be an array with the responses that you can use on the DrinkList function.
Here's a method which will let you specify the cocktail names as dependencies to the useEffect so you can store them in your state and fetch new drink lists if you want new recipes. If not, it'll just be a static state variable.
I've also added another state variable errorMessage which you use to pass an error message in the case of failure.
Also, you should include the appropriate dependencies in your useEffect hook. The setState functions returned by calls to useState are stable and won't trigger a re-run of the effect, and the cocktailNames variable won't trigger a re-run unless you update it with new things to fetch.
const DataContextProvider = (props) => {
const [isLoading, setLoading] = useState(false);
const [cocktails, setCocktails] = useState([]);
const [errorMessage, setErrorMessage] = useState(''); // holds an error message in case the network request dosn't succeed
const [cocktailNames, setCocktailNames] = useState(['margarita', 'martini']); // the search queries for the `s` parameter at your API endpoint
useEffect(() => {
const fetchCocktailLists = async (...cocktailNames) => {
const fetchCocktailList = async (cocktailName) => {
const baseUrl = 'https://www.thecocktaildb.com/api/json/v1/1/search.php';
const url = new URL(baseUrl);
const params = new URLSearchParams({s: cocktailName});
url.search = params.toString(); // -> '?s=cocktailName'
const res = await fetch(url.href); // -> 'https://www.thecocktaildb.com/api/json/v1/1/search.php?s=cocktailName'
const data = await res.json();
const {drinks: drinkList} = data; // destructured form of: const drinkList = data.drinks;
return drinkList;
};
setLoading(true);
try {
const promises = [];
for (const cocktailName of cocktailNames) {
promises.push(fetchCocktailList(cocktailName));
}
const drinkLists = await Promise.all(promises); // -> [[drink1, drink2], [drink3, drink4]]
const allDrinks = drinkLists.flat(1); // -> [drink1, drink2, drink3, drink4]
setCocktails(allDrinks);
}
catch (err) {
setErrorMessage(err.message /* or whatever custom message you want */);
}
setLoading(false);
};
fetchCocktailList(...cocktailNames);
}, [cocktailNames, setCocktails, setErrorMessage, setLoading]);
};
var promises = [
fetchCocktail(api1),
fetchCocktail(api2)
];
var results = await Promise.allSettled(promises);

Loop through async requests with nested async requests

I have a scenario where I am calling an API that has pagination.
What I'd like to do is the following, 1 page at a time.
Call API Page 1
For each of the items in the response, call a Promise to get more data and store in an array
Send the array to an API
Repeat until all pages are complete
What I currently have is the following, however I think I am possibly complicating this too much, although unsure on how to proceed.
export const importData = async() {
const pSize = 15;
const response = await getItems(pSize, 1);
const noPage = Math.ceil(response.totalMerchandiseCount/pSize);
for (let i = 1; i < noPage; i++) {
const items = [];
const data = await getItems(pSize, i);
await async.each(data.merchandiseList, async(i, cb) => {
const imageURL = await getImageURL(i.id, i.type);
items.push({
id: i.id,
imageURL: imageURL,
});
cb();
}, async() => {
return await api.mockable('sync', items);
});
}
}
export const getImageURL = async(id, type) => {
let url = `https://example.com/${id}`;
return axios.get(url)
.then((response) => {
const $ = cheerio.load(response.data);
// do stuff to get imageUrl
return image;
})
.catch((e) => {
console.log(e);
return null;
})
};
The issue I have at the moment is that it seems to wait until all pages are complete before calling api.mockable. Items is also empty at this point.
Can anyone suggest a way to make this a bit neater and help me get it working?
If this is all meant to be serial, then you can just use a for-of loop:
export const importData = async() {
const pSize = 15;
const response = await getItems(pSize, 1);
const noPage = Math.ceil(response.totalMerchandiseCount/pSize);
for (let i = 1; i < noPage; i++) { // Are you sure this shouldn't be <=?
const items = [];
const data = await getItems(pSize, i);
for (const {id, type} of data.merchandiseList) {
const imageURL = await getImageURL(id, type);
items.push({id, imageURL});
}
await api.mockable('sync', items);
}
}
I also threw some destructuring and shorthand properties in there. :-)
If it's just the pages in serial but you can get the items in parallel, you can replace the for-of with map and Promise.all on the items:
export const importData = async() {
const pSize = 15;
const response = await getItems(pSize, 1);
const noPage = Math.ceil(response.totalMerchandiseCount/pSize);
for (let i = 1; i < noPage; i++) { // Are you sure this shouldn't be <=?
const data = await getItems(pSize, i);
const items = await Promise.all(data.merchandiseList.map(async ({id, type}) => {
const imageURL = await getImageURL(id, type);
return {id, imageURL};
}));
await api.mockable('sync', items);
}
}
That async function call to map can be slightly more efficient as a non-async function:
export const importData = async() {
const pSize = 15;
const response = await getItems(pSize, 1);
const noPage = Math.ceil(response.totalMerchandiseCount/pSize);
for (let i = 1; i < noPage; i++) {
const data = await getItems(pSize, i);
const items = await Promise.all(data.merchandiseList.map(({id, type}) =>
getImageURL(id, type).then(imageURL => ({id, imageURL}))
));
await api.mockable('sync', items);
}
}

Categories