I have a promise which return an array of objects from IndexedDB:
const syncData = () => {
return new Promise((resolve, reject)=>{
var all_form_obj = [];
var db = self.indexedDB.open('Test');
db.onsuccess = function(event) {
var db = this.result
// Table new_form
var count_object_store_new_form = this.result.transaction("new_form").objectStore("new_form").count()
count_object_store_new_form.onsuccess = function(event) {
if(count_object_store_new_form.result > 0){
db.transaction("new_form").objectStore("new_form").getAll().onsuccess = function(event) {
var old_form_arr = event.target.result
for(element in old_form_arr){
all_form_obj.push(old_form_arr[element])
}
}
}
}
// Table old_form
var count_object_store_old_form = this.result.transaction("old_form").objectStore("old_form").count()
count_object_store_old_form.onsuccess = function(event) {
if(count_object_store_old_form.result > 0){
db.transaction("old_form").objectStore("old_form").getAll().onsuccess = function(event) {
var old_form_arr = event.target.result
for(element in old_form_arr){
all_form_obj.push(old_form_arr[element])
}
}
}
}
}
db.onerror = function(err) {
reject(err);
}
resolve(all_form_obj)
})
};
After I resolve my array, I call the promise in the sync event:
self.addEventListener('sync', function(event) {
if (event.tag == 'sync_event') {
event.waitUntil(
syncData()
.then((form_arr)=>{
console.log(form_arr)
for(form in form_arr) {
console.log(form_arr)
}
}).catch((err) => console.log(err))
);
}
});
In the 'then' of my promise syncData I print to the console two times.
The first console.log appears in the console (my array of objects) but the second which is in loop (for in) doesn't appear in the console and I don't understand why.
My goal is to be able to loop through each object and send it to my database with fetch but the problem is that the code in the loop doesn't run.
My result of the first console log:
I think resolve is not placed in the desired place and the reason why the 2nd console.log is not showing up is because form_arr is []. I simplified your code to demonstrate why it went []. db.onsuccess and db.onerror were just defined there without being called. To fix this problem, you may want to place resolve inside db.onsuccess and reject inside db.onerror.
const syncData = () => {
return new Promise((resolve, reject)=>{
var all_form_obj = [];
var db = self.indexedDB.open('Test');
db.onsuccess = function(event) {
// ... will be called async
resolve(all_form_obj)
}
db.onerror = function(err) {
// ... will be called async
}
// remove resolve here
})
};
Related
I am trying to make api call and store the results in array. function itself is an async. Here's a code.
async function setPoliciesData(policies) {
let tmpPolicies = {};
await policies.forEach((policy) => {
const tmp = { ...policy };
// Here I need help. This returns promise Instade remembers How to wait till promise finishes
tmp["Name"] = getPolicyNameFromLocalStrage(policy.id);
try {
if (policy?.audience?.id) {
tmp["members"] = getMembersFromAudienceId(policy.audience.id);
} else {
tmp["members"] = [];
}
} catch (e) {
console.log(e);
}
let id = policy.id;
console.log("Setting policy ID : " + policy.id);
tmpPolicies[policy.id] = tmp;
});
console.log("Done the processing");
return tmpPolicies;
}
I am getting Promise object in return. I would want members returnd array.
I tried to console log and I am seeing that issue seems to be because of method is not async. What is proper way to fix it.
I refactored some of your code, but if you should make the function inside of the forEach asynchronous. In this case, I changed it to map to be a bit easier to follow. The key at the end is to return Promise.all() which will wait for all of the inner promises to be resolved before returning:
async function setPoliciesData(policies) {
const tmpPolicies = policies.map(async (policy) => {
policy.Name = await getPolicyNameFromLocalStrage(policy.id);
try {
policy.members = policy.audience && policy.audience.id
? await getMembersFromAudienceId(policy.audience.id)
: [];
} catch (e) {
console.error('Error: ' + e);
}
return policy;
});
console.log("Done the processing");
return Promise.all(tmpPolicies);
}
When the code below runs I expect score to have a value (lets say: {"A": 1, "B": 2}), but when I print it I get an empty dict ({}).
I have tried to use promises but the result is the same.
driversBySeason(season) {
var query = `SELECT raceId FROM races WHERE year = ${season}`;
var score = {};
this.con.query(query, (err, drop) => {
if (err) {
console.error(err);
}
drop.forEach((element) => {
var raceId = element["raceId"];
query = `SELECT driverId, points FROM results WHERE raceId = ${raceId}`;
this.con.query(query, (err, drop) => {
if (err) {
console.error(err);
}
drop.forEach((element) => {
if (score[element["driverId"]] == undefined) {
score[element["driverId"]] = 0;
} else if (score[element["points"]] != undefined) {
score[element["driverId"]] += element["points"];
}
});
});
});
console.log(score);
});
}
First of all you need to change your .forEach loop to for const of loop or just a regular for loop because it just fires the code inside the callback and never waits for it. And then you need to change your this.con.query(...) function which has callback to promises too. Your code should be like this:
const asyncQuery = (query) => new Promise((resolve, reject) => {
this.con.query(query, (err, drop) => {
if (!!err) {
console.log(error);
return reject(err)
}
return resolve(drop);
})
});
async function driversBySeason(season) {
var query = `SELECT raceId FROM races WHERE year = ${season}`;
var score = {};
const drop = await asyncQuery(query).catch(err => err /* some error handling */);
for (const element of drop) {
var raceId = element["raceId"];
query = `SELECT driverId, points FROM results WHERE raceId = ${raceId}`;
const drop2 = await asyncQuery(query).catch(err => err /* some error handling */);
drop2.forEach((element) => {
if (score[element["driverId"]] == undefined) {
score[element["driverId"]] = 0;
} else if (score[element["points"]] != undefined) {
score[element["driverId"]] += element["points"];
}
});
}
console.log(score);
}
You are outside of your callback. When this executes the code continues to run past "this.con.query". You are seeing your empty object that was assigned at the top do to this. Go inside the callback after the drop.forEach where you assign the values, or convert to an async/await approach.
when you call driversBySeason your score variable has value of {}
and that is value for console.log() when you call it because of how closers are works and you updating score with callback function that happen later in time...
you get better understanding if you use promise...not callback
I tried to prevent async problems with promises in the following code. By using a .then function everything within that function gets called after the function has been resolved. But now I have the problem that neither can I extend the scope of the ".then function" enough to include the bits after the second loop nor can I to my knowledge easily pause the code until the function has been properly resolved and THEN continue with the loop iteration.
Here's my main code(simplified):
let total = []
$.each(element, function(data) {
//Some other code
let out;
$.each(element2, function(data2) {
getZip(data2).then(function(txt){ //after everything has finished this get's called
out = someFunction(txt,data2);
total.push(out);
});
)};
console.log(total)//this gets called first
//some other code that does some stuff with total
)};
Here's the getZip code which is asynchronous:
function getZip(zipFile) {
return new Promise(function (resolve, reject){
zip = new JSZip()
JSZipUtils.getBinaryContent("someURL/" + zipFile, function (err, data) {
if (err) {
reject(err)
}
JSZip.loadAsync(data).then(function (zip) {
return zip.file(zipFile.replace(".zip", "")).async("text"); //gets the file within the zip andoutputs as text
}).then(function (txt) {
resolve(txt)
});
});
});
}
I'd be happy if either the getZip code could be made synchronous or if the before mentioned could be done.
I do not think I fully understand the code you have written. However, I recommend you use Promise.all. Here is an example I have written that I hope helps guide you:
let total = [];
$.each([1,2,3,4], function (data) {
// Some other code.
let out;
// Create a new promise so that we can wait on the getZip method.
new Promise(function (resolve, reject) {
// Create a holder variable. This variable with hold all the promises that are output from the getZip method you have.
let gZipPromises = [];
$.each([5,6,7,8], function (data2) {
// Your getZip method would go here. wrap the call to getZip in gZipPromises.push to push all the returned promises onto the holding variable.
gZipPromises.push(new Promise(function (resolve2, reject2) {
// Sample Code
setTimeout(function () {
total.push(data2);
resolve2("");
}, 10);
// End Sample Code.
}));
});
// Pass the holding variable to Promise.all so that all promises in the holding variable are executed before resolving.
Promise.all(gZipPromises).then(function() {
resolve()
});
}).then(function () {
// This will be called only when all getZip promises are completed in the second loop.
console.log(total);
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
With that said, I could not test your code. But I think this would work:
(Please note that based on the code you provided, the variable total would be logged for each iteration of the top most $.each
let total = []
$.each(element, function(data) {
//Some other code
let out;
// Define a new promise.
new Promise(function (resolve, reject) {
let gZipPromises = [];
$.each(element2, function(data2) {
gZipPromises.push(
getZip(data2).then(function(txt){ //after everything has finished this get's called
out = someFunction(txt,data2);
total.push(out);
});
);
)};
Promise.all(gZipPromises).then(function() {
resolve()
});
}).then(function () {
console.log(total)
});
)};
const elements = [["foo.zip"],["bar.zip"],["baz.zip"]];
const totalOut = getAllZips(elements)
.then(text => console.info(text))
.catch(error => console.error(error))
function someFunction(text, data) {
return `${text}\nLength: ${data.length}`;
}
async function getAllZips(elements) {
let promises = [];
for(const element of elements) {
for(const data of element) {
promises.push(getZip(data).then(text => {
return someFunction(text, data);
}));
}
}
return Promise.all(promises);
}
async function getZip(file) {
return new Promise((resolve, reject) => {
JSZipUtils.getBinaryContent(`someURL/${file}`, async (err, data) => {
try {
if (err) throw err;
const zip = await JSZip.loadAsync(data);
const name = file.replace(".zip", "");
resolve(await zip.file(name).async('text'));
} catch(error) {
reject(error);
}
});
});
}
<script>/*IGNORE*/const JSZipUtils = {getBinaryContent:(p,c)=>errs.gbc?c(new Error('gbc'),null):c(null,{foo:true})};const JSZip = {loadAsync:(d)=>errs.la?Promise.reject(new Error('la')):({file:n=>({async:a=>errs.a?Promise.reject(new Error('a')):Promise.resolve('Hello World')})})};const errs = {gbc:false,la:false,a:false};/*IGNORE*/</script>
This kind of sounds like a use case for async iterator generators, but maybe I'm just over-engineering. You have a bunch of resources that you want to iterate over and their contents are asynchronous. You want it to "look" synchronous, so you can leverage async/await:
function getZip(zipFile) {
/*
* Theres no point in simplifying this function since it looks like
* the JSZip API deals with callbacks and not Promises.
*/
return Promise.resolve(zipFile);
}
function someFn(a, b) {
return `${a}: ${b.length}`;
}
async function* zipper(elements) {
for (const element of elements) {
for (const data of element) {
const txt = await getZip(data);
yield someFn(txt, data);
}
}
}
(async() => {
const elements = [
["hello"],
["world"],
["foo"],
["bar"]
];
let total = [];
for await (const out of zipper(elements)) {
total.push(out);
}
console.log(total);
})();
I am trying to do the following actions in order:
1. Create filename
2. Copy that filename to the clipboard
3. Open a window to a specific url
4. Click a specific div on the new window
5. Close the window
6. Now, I can print and simply paste the copied filename and finally print to pdf
I need these actions to happen one after the other. I need to run this logic inside of a loop (or at least execute it 200 times). So I need these steps to happen and THEN move to the next item in the html table that I am traversing.
let obj = {};
const copyToClipboard = (text) => {
const tempElem = document.createElement('input');
document.body.appendChild(tempElem);
tempElem.setAttribute('value', text);
tempElem.select();
document.execCommand("copy");
document.body.removeChild(tempElem);
};
const traverse = (i, inObj) => {
const number = document.getElementById('nid').rows[i].getElementsByTagName('td')[1].innerText;
const status = document.getElementById('nid').rows[i].getElementsByTagName('td')[4].innerText;
const file = `Item: ${name}`;
if(inObj[number] == "undefined" || inObj[poName] != status){
inObj[number] = status;
copyToClipboard(file);
let url = 'mysite.com/myroute';
let a = window.open(url);
a.focus();
let timer = setInterval(() => {
a.document.getElementsByClassName('anotherid')[1].click();
const i = a.document.getElementById('myframe');
i.contentWindow.focus();
i.contentWindow.print();
a.close();
clearInterval(timer);
console.log('Success!');
}, 1000);
} else{
console.log('Failure!');
}
};
for(let i = 0; i < tableSize; i++{
traverse(i, obj);
}
Some pieces of my code will execute before the others. For example, the windows will all open at once and then the remaining actions will take place. I need this code to execute completely inside of a loop before the next index iteration.
The only asynchronous thing I see in your code is traverse's completion. So simply define traverse to return a promise. Given the code in traverse, this is probably easiest if you do it explicitly:
const traverse = (i, inObj) => {
return new Promise((resolve, reject) => { // <===========================
const number = document.getElementById('nid').rows[i].getElementsByTagName('td')[1].innerText;
const status = document.getElementById('nid').rows[i].getElementsByTagName('td')[4].innerText;
const file = `Item: ${name}`;
if(inObj[number] == "undefined" || inObj[poName] != status){
inObj[number] = status;
copyToClipboard(file);
let url = 'mysite.com/myroute';
let a = window.open(url);
a.focus();
let timer = setInterval(() => {
a.document.getElementsByClassName('anotherid')[1].click();
const i = a.document.getElementById('myframe');
i.contentWindow.focus();
i.contentWindow.print();
a.close();
clearInterval(timer);
console.log('Success!');
resolve(); // <===========================
}, 1000);
// *** Probably want to do a timeout here in case the window never loads
} else{
console.log('Failure!');
reject(); // <===========================
}
});
};
Then:
If you can use async syntax, write your loop using await on the promise returned by traverse. Example:
// In an `async` function
try {
for (let i = 0; i < tableSize; ++i) {
await traverse(i, obj);
}
// Done
} catch (error) {
// An error occurred
}
If not, chain your operations together by calling then on the promise returned by traverse:
let promise = Promise.resolve();
for (let i = 0; i < tableSize; ++i) {
promise = promise.then(() => traverse(i, obj));
}
promise
.then(() => {
// Done
})
.catch(error => {
// An error occurred
};
Updating `tra
there is a function that I use to read all files in a directory and then sent an object with emitter to the client.
this is my code that works fine,
const getFilesList = (path, emitter) => {
fs.readdir(path, (err, files) => {
emitter('getFileList', files);
});
};
but when I want to filter hidden files with this code, the 'standardFolders' will send empty in the emitter.
const getFilesList = (path, emitter) => {
let standardFolders = [];
fs.readdir(path, (err, files) => {
if (files) {
files.map((file) => {
winattr.get(path + file, function (err, attrs) {
if (err == null && attrs.directory && (!attrs.hidden && !attrs.system)) {
standardFolders.push(file)
}
});
});
} else {
standardFolders = null;
}
emitter('getFileList', standardFolders);
});
};
what is wrong with my code in the second part?
winattr.get(filepath,callback) is asynchronous, so imagine your code "starts" the file.map() line and then immediately skips to emitter('getFileList',standardFolders) --- which standardFolders is empty because it hasn't finished yet!
You can use a library like async.io to handle your callback functions, or you can use a counter and keep track of when all of the callbacks (for each file) has finished yourself.
Example:
// an asynchronous function because setTimeout
function processor(v,cb){
let delay = Math.random()*2000+500;
console.log('delay',delay);
setTimeout(function(){
console.log('val',v);
cb(null,v);
},delay);
}
const main = function(){
const list = ['a','b','c','d'];
let processed = [];
let count = 0;
console.log('starting');
list.map(function(v,i,a){
console.log('calling processor');
processor(v,function(err,value){
processed.push(v);
count+=1;
console.log('count',count);
if(count>=list.length){
// all are finished, continue on here.
console.log('done');
}
})
})
console.log('not done yet!');
};
main();
Similarly, for your code:
const getFilesList = (path, emitter) => {
let standardFolders = [];
fs.readdir(path, (err, files) => {
if (files) {
let count = 0;
files.map((file) => {
winattr.get(path + file, function (err, attrs) {
if (err == null && attrs.directory && (!attrs.hidden && !attrs.system)) {
standardFolders.push(file)
}
count+=1;
if(count>=files.length){
// finally done
emitter('getFileList', standardFolders);
}
});
});
} else {
standardFolders = null;
emitter('getFileList', standardFolders);
}
});
};
As already sayed in the other answer winattr.get is async, so the loop finishes before any of the callbacks of winattr.get is called.
You could convert your code using async/await and primitify into a code that looks almost like a sync version, and you can completely get rid of the callbacks or counters
const {promisify} = require('util')
const readdir = promisify(require('fs').readdir)
const winattrget = promisify(require('winattr').get)
const getFilesList = async (path, emitter) => {
let standardFolders = [];
try {
let files = await readdir(path);
for (let file of files) {
try {
let attrs = await winattrget(path + file)
if (attrs.directory && (!attrs.hidden && !attrs.system)) {
standardFolders.push(file)
}
} catch (err) {
// do nothing if an error occurs
}
}
} catch (err) {
standardFolders = null;
}
emitter('getFileList', standardFolders);
};
An additional note: In your code you write files.map, but mapping is use to transform the values of a given array and store them in a new one, and this is not done in your current code, so in the given case you should use a forEach loop instead of map.