I am new to javascript async/await etc. The code below has been stripped of any unimportant parts.
The function addAllocationToQueue is invoked from a button press in an electron app. This makes an axios request and creates a new allocation item which belongs to a user. After this has completed, populateAllocationQueue is invoked which updates the DOM to reflect the new allocation item that's been added, but this is where I am having trouble... In this method I am looping through the allocations in the allocationQueue array. for each of the allocations I wish to make a new api call which will return the user that this allocation belongs to, but the problem is that the axios request inside the for loop is not firing.
Am i handling the async functions correctly?
const axios = require('axios')
var currentSandboxId = '1';
var allocationQueue = []
const setCurrentSandboxId = async (sbId) => {
currentSandboxId = sbId;
const response = await axios.get(`http://localhost:3000/allocations/all-for-sandbox/${currentSandboxId}`)
allocationQueue = response.data
await populateAllocationQueue()
}
const addAllocationToQueue = async () => {
const response = await axios.post('http://localhost:3000/allocations/', {
sandboxId: currentSandboxId,
branchName: 'test-branch',
userId: '123456789'
})
await populateAllocationQueue()
}
const populateAllocationQueue = async () => {
for(let j = 0; j < allocationQueue.length; j++) {
let allocation = allocationQueue[j];
//some DOM manipulation
const res = await axios.get('http://localhost:3000/users', { _id: allocation.createdBy })
console.log('Never gets called - seems to hang on the above request without error message')
}
}
setCurrentSandboxId('6');
Related
What's up friends,
I have a problem. I have DOM elements that show data from the variable (useState) that I am saving with some "accounts".
I have a function that updates the data of each element by calling the api, and I have a function that refreshes all the elements one by one, the problem is that when using the function that refreshes all the elements, the changes are not reflected correctly in the dom but when using the function individually (I have a button for that) I can see the changes correctly reflected.
I do not get the error, if you can help me I would be very grateful.
Refresh Function
let [accounts, setAccounts] = useState
const refreshAccount = async (id, name, token) => {
await setIsBusy(true)
await updateWorkingStatus(id, true, name, token)
let work = await axios.get(
'http://192.168.0.101:3000/account/refresh/' + id,
)
await updateWorkingStatus(id, false, name, token)
await setIsBusy(false)
// console.log(work.data.result)
let accs = [...accounts]
let index = accs.findIndex((acc) => acc._id === id)
accs[index] = work.data.result
accs[index]._id = id
// console.log(accs[index])
setTimeout(() => {
setAccounts(accs)
}, 0)
return 'OK'
}
Refresh Selected Item (I already have a function that selects each item and works nice)
const refreshSelected = async () => {
let finalAccounts = await accounts.filter((acc) => acc.selected)
for (let i = 0; i < finalAccounts.length; i++) {
await refreshAccount(
finalAccounts[i]._id,
finalAccounts[i].name,
finalAccounts[i].token,
)
}
}
I know the problem is updating the status, but I can't solve it. Implement a setTimeout and the previous element to the last is updated or the last of the cycle, the previous ones are left with the outdated data (first state).
I think problem is when using FOR loop on React, but dont know how to use it correctly
The problem is probably that the setAccounts function is asynchronous, but the execution doesn't wait for it to complete. Because setAccounts is asynchronous, subsequent calls in the same update cycle will overwrite previous updates, and the previous changes will be lost, causing only the last update to take effect.
You could store the changes to an array and update it after all the API requests have finished.
let [accounts, setAccounts] = useState
const refreshAccount = async (id, name, token) => {
await setIsBusy(true)
await updateWorkingStatus(id, true, name, token)
let work = await axios.get(
'http://192.168.0.101:3000/account/refresh/' + id,
)
await updateWorkingStatus(id, false, name, token)
await setIsBusy(false)
// console.log(work.data.result)
let accs = [...accounts]
let index = accs.findIndex((acc) => acc._id === id)
accs[index] = work.data.result
accs[index]._id = id
return accs
}
const refreshSelected = async () => {
let finalAccounts = await accounts.filter((acc) => acc.selected)
let newAccounts = []
for (let i = 0; i < finalAccounts.length; i++) {
const accs = await refreshAccount(
finalAccounts[i]._id,
finalAccounts[i].name,
finalAccounts[i].token,
)
newAccounts = newAccounts.concat(accs)
}
setAccounts(newAccounts)
}
I've read multiple posts on here about reading in table data, however, every post is how to read and use that data within a single test function. How do I read a table in, and use it in any test function within a test spec?
If I write this into a test function it works and gives back all the info correctly:
import { sAdmin } from "..authentication";
import TablePage from "../TablePage";
const tablePage = new TablePage();
fixture `A fixture`
.page`https://subdomain.example.com/#/table`
.beforeEach( async t => {
await t
.resizeWindow(1284, 722)
.useRole(sAdmin);
});
// this will work, but this won't share context with any other test function in the spec
// this should be initialized at the start to be able to verify search clears
test(`A test`, async t => {
const tableColumn = await tablePage.woNumber;
const tableCount = await tablePage.woNumber.count;
const cellData = [];
for (let i = 0; i < tableCount; i++) {
const text = await tableColumn.nth(i).innerText;
cellData.push(text);
}
console.log('in spec - tableCount', tableCount);
console.log('in spec - cellData', cellData);
});
The output of both console logs is correct:
in spec - tableCount 4
in spec - cellData [ '0007', '0003', '0005', '0006' ]
I've tried an async function in my test spec, and in my page object model (POM). Async function won't work in my spec unless it's within a test function. The one in the POM works, it'll get called, however i can't do const tableCount = await tablePage.woNumber.count; it will yell at me that I can't use a selector like that. It's because of the modifier .count. I adjusted the .count to be within the for loop but that just returned undefined or other data that didn't help.
Example of the async function in my page object model (TablePage)
async rowCount() {
const tableColumn = await tablePage.fooSelector;
const tableCount = await tablePage.fooSelector;
const cellData = [];
for (let i = 0; i < tableCount.count; i++) {
const text = await tableColumn.nth(i).innerText;
cellData.push(text);
}
console.log('in page - tableColumn', tableColumn);
console.log('in page - tableCount', tableCount);
console.log('in page - cellData', cellData);
return tableCount;
};
It's called with this in my spec file, not sure where to call it though:
const count = tablePage.rowCount();
I need this to run after the page has loaded to grab the cell data, and allow me to share context across all tests within this spec file at the very minimum. I'd prefer to put it in my POM, so it can be used elsewhere in other tests. But I'd settle for it working in my test spec without it being in a test function so it can be shared across all tests in the spec file.
I've tried to do a fixture context, but that also had issues and returned undefined. Here is a before with context that I tried, that didn't work.
.before( async ctx => {
const tableColumn = await tablePage.fooSelector;
const tableCount = await tablePage.fooSelector;
for (let i = 0; i < tableCount.count; i++) {
const text = await tableColumn.nth(i).innerText;
ctx.cellData.push(text);
}
// console.log('in spec - tableCount', tableCount);
// console.log('in in spec - cellData', cellData);
})
These console logs return undefined, or objects instead of the text.
Any help would be greatly appreciated. Here are resources I referenced already:
TestCafe - Storing results of Selector in variable
How do I can get a text of all the cells of the table using testcafe
Testcafe get text from element
https://testcafe.io/documentation/402670/reference/test-api/domnodestate
EDIT: I'm still looking for a way to share context of the data I get, I wasn't able to return the data back to the test spec. Maybe if I do more tinkering I can share the values I've obtained.
Here is my solution that doesn't share context, but it does let me prevent code reuse in every test function.
Page Object Model
import { Selector, t } from "testcafe";
class TablePage{
constructor() {
this.searchInput = Selector('#searchInput');
this.tableCount = Selector('.class-selector');
};
async validateSearchResults(selector, searchText) {
await t
.typeText(this.searchInput, searchText)
.pressKey('enter');
const rowCount = await this.tableCount.count;
let searchResults = []
for (let i = 0; i < rowCount; i++) {
let text = await selector.nth(i).innerText;
searchResults.push(text);
await t.expect(searchResults[i]).contains(searchText);
}
};
}
export default TablePage;
Spec File
import { sAdmin } from "..authentication";
import TablePage from "../TablePage";
const tablePage = new TablePage();
fixture `Test search functionality`
.page`https://examplepage.com`
.beforeEach( async t => {
await t
.useRole(sAdmin)
});
test(`User can search via order number`, async t => {
await tablePage.validateSearchResults(tablePage.tableCount, 'foo');
});
The const tableCount = await tablePage.selector.count; looks correct and should work. Also, it's necessary to call the async method with await keyword:
const count = await tablePage.rowCount();
Here is an example of a similar approach:
table-page.js:
import { Selector } from 'testcafe';
export class TablePage {
constructor () {
this.tableCells = Selector('#ContentHolder_grid_DXDataRow0 td');
this.cellData = [];
this.cellCount = 0;
}
async initCellData () {
this.cellCount = await this.tableCells.count;
for (let i = 0; i < this.cellCount; i++) {
const text = await this.tableCells.nth(i).innerText;
this.cellData.push(text);
}
}
}
test.js:
import { TablePage } from './table-page';
let page = null;
fixture `New Fixture`
.page `https://demos.devexpress.com/ASPxGridViewDemos/DataBinding/QueryBuilderControl.aspx`
.beforeEach(async () => {
page = new TablePage();
await page.initCellData();
});
test(`New Test`, async t => {
console.log('cells count: ', page.cellCount);
console.log('cells data: ', page.cellData);
});
I'm new to both firebase and async javascript. I'm trying to create a scheduled task in firebase to fetch a bunch of rss URLs from a collection, parse it and store it inside another collection but I'm getting Error: 4 DEADLINE_EXCEEDED: Deadline exceeded at Object.callErrorFromStatus upon its execution.
const refreshRSS = functions.pubsub.schedule('every 30 mins').onRun(async context => {
let newRSS = addRSS();
return await newPodcasts;
});
addRSS = async () => {
const newRSSFeed = new Array();
let rssURLs = await db.collection('rssURLs').get();
rssURLs.forEach(async rssURLsObject=>{
rss = rssURLsObject.data();
let rssData = await parser.parseURL(rss.url);
newRSSFeed.push(db.collection('rss').doc(encodeURIComponent(rss.url))
.set(podcast));
})
return newRSSFeeds;
}
I also tried returning Promise.all(newRSS) inside refreshRSS but it throws another error stating: is not iterable.
I'm not sure what exactly is firebase expectingas return parameter.
When you have to resolve promise in loop, try to use map. This will work:
const refreshRSS = functions.pubsub.schedule('every 30 mins').onRun(async context => {
let newRSS = await addRSS();
return await newPodcasts;
});
addRSS = async () => {
const newRSSFeed = new Array();
let rssURLs = await db.collection('rssURLs').get();
let promise = rssURLs.map(async rssURLsObject=>{
rss = rssURLsObject.data();
let rssData = await parser.parseURL(rss.url);
let con = await db.collection('rss').doc(encodeURIComponent(rss.url)
newRSSFeed.push(con)
.set(podcast));
})
await Promise.all(promise)
return newRSSFeeds;
}
I'm trying to improve a firestore get function, I have something like:
return admin.firestore().collection("submissions").get().then(
async (x) => {
var toRet: any = [];
for (var i = 0; i < 10; i++) {
try {
var hasMedia = x.docs[i].data()['mediaRef'];
if (hasMedia != null) {
var docData = (await x.docs[i].data()) as MediaSubmission;
let submission: MediaSubmission = new MediaSubmission();
submission.author = x.docs[i].data()['author'];
submission.description = x.docs[i].data()['description'];
var mediaRef = await admin.firestore().doc(docData.mediaRef).get();
submission.media = mediaRef.data() as MediaData;
toRet.push(submission);
}
}
catch (e) {
console.log("ERROR GETTIGN MEDIA: " + e);
}
}
return res.status(200).send(toRet);
});
The first get is fine but the performance is worst on the line:
var mediaRef = await admin.firestore().doc(docData.mediaRef).get();
I think this is because the call is not batched.
Would it be possible to do a batch get on an array of mediaRefs to improve performance?
Essentially I have a collection of documents which have foreign references stored by a string pointing to the path in a separate collection and getting those references has been proven to be slow.
What about this? I did some refactoring to use more await/async code, hopefully my comments are helpful.
The main idea is to use Promise.all and await all the mediaRefs retrieval
async function test(req, res) {
// get all docs
const { docs } = await admin
.firestore()
.collection('submissions')
.get();
// get data property only of docs with mediaRef
const datas = await Promise.all(
docs.map(doc => doc.data()).filter(data => data.mediaRef),
);
// get all media in one batch - this is the important change
const mediaRefs = await Promise.all(
datas.map(({ mediaRef }) =>
admin
.firestore()
.doc(mediaRef)
.get(),
),
);
// create return object
const toRet = datas.map((data: MediaSubmission, i) => {
const submission = new MediaSubmission();
submission.author = data.author;
submission.description = data.description;
submission.media = mediaRefs[i].data() as MediaData;
return submission;
});
return res.status(200).send(toRet);
}
Question is simple:
Example:
For (iterate based on amount of cores){
Let worker = workers[I]
Worker.postmessage
}
End of example .
Disclaimer: This example only shows what is expected of the end result and is in no means in what is considered "working condition" . Also note that the method used above does not return a worker for "workers[iterator]" instead just undefined.
Objective: Create working methods:
1: make array of unknown amount of workers(based on cores).
2: once that array is built, post a message to each worker and have a returned result(other than undefined).
Note: I do have a hypothesis of why it does not work:
1: web workers are created and are only accessable through the event that created them and its only acception is the onmessage "event" handler .
in defiance of my hypothesis there is such things that would say neigh to what is written above for example , like thread.js that allows for thread pooling and other procedures.
This is the main reason of why I ask , because I do know it is possible but would like a simple answer.
Thanks for your time .
Here is an example:
function createWorker (workerScript) {
const blob = new Blob([`(${workerScript})(self)`], {type: 'application/javascript'});
return new Worker(URL.createObjectURL(blob));
};
function workerCode (self) {
self.onmessage = function (message) {
postMessage(`Data from worker: ${message.data}`);
};
};
// assuming that you will send only one message to the worker,
// and that the worker will produce only one message too.
function workerPromise (worker, message) {
const promise = new Promise((resolve, reject) => {
worker.onmessage = resolve;
}).then(message => message.data);
worker.postMessage(message);
return promise;
}
(async () => {
const workers = [];
for (let i = 0; i < navigator.hardwareConcurrency; i++) {
workers.push(createWorker(workerCode));
}
const results = await Promise.all(
workers.map((w, index) => workerPromise(w, `worker ${index}`))
);
console.log(results);
})();
Start with the "Examples" section under: https://developer.mozilla.org/en-US/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency
Modified example:
// in Main thread
const numberOfCPUCores = window.navigator.hardwareConcurrency;
const workerList = [];
const cpuWorkerMessageHandler = event => {
// process message from a worker by accessing: event.data
}
for (let i = 0; i < numberOfCPUCores; i++) {
const newWorker = new Worker('cpuworker.js');
newWorker.addEventListener("message", cpuWorkerMessageHandler);
workerList.push(newWorker);
}
// then, when done with all processing, terminate all workers
workerList.forEach(w => w.terminate());