i know that there are plenty of posts on this topic, but i couldn't figure it out. I'm new to all this promise-thing, but i got a specific problem that needs to be solved. In the code below i loop through an array 'fnFoes' and within this fnFoes trough another array 'avatar'. For each element in avatar an svg-Code from an indexedDB is loaded and appended to html. My problem is: I want this function to fully load and append all svg before finishing the function and going on to the next function.
I tried to wrap the entire function into a promis but it still went on before having loaded all svg. I assume that this has to do with the indexedDB, but honestly, i just don't get it.
After 2 days of post reading and trial and error, Any help is much appreciated!!
Thx
Fritz
$.map(fnFoes, function(i,obj){
console.log(obj);
console.log('loadAllAvatars: checking foe: ' + obj);
if (!(obj in foes) || fnFoes[obj].avatar != foes[obj].avatar || document.getElementById(obj) === null){ // compare avatar information of both variables. if changed, render new
console.log('loadAllAvatars: foe '+ obj +' will be rendered');
foeRenderIds.push(obj); // push foe id in list
if (obj in foes) {
foes[obj].avatar = fnFoes[obj].avatar; // change avatar items in foes;
}
var avatar = fnFoes[obj].avatar.split(','); // split avatar string in array
console.log(avatar);
console.log(fnFoes[obj]);
if (document.getElementById('#'+obj)){ // if foe div already exists in avatarDoubles, just delete it
$('#'+obj).html(); // delete old avatar from doubles
} else {
var html ='<div id="foe' + obj + '"></div>'; // if avatar doesn't exist, create it in avatarDoubles
$('#avatarDoubles').append(html);
}
//render avatar
if (typeof avatar !== 'undefined' && avatar.length > 1) {
$.map(avatar, function(j,key){
console.log(avatar[key]);
var name = avatar[key];
db.svgs.get(name).then(function(obj2){
var html = "<div class='" + obj2.category + "' data-name='" + obj2.name + "' data-category='" + obj2.category + "'>" + obj2.svg + "</div>";
$('#foe' + obj).append(html);
console.log(obj2.name);
});
});
}
} else {
console.log('loadAllAvatars: foe '+ obj +' already exists');
}
});
I am struggling to understand your code but you probably want something like this:
function connect() {
return new Promise(function(resolve, reject){
var request = indexedDB.open();
request.onsuccess = () => resolve(request.result);
});
}
function dothings(db, things) {
var promises = [];
for(var thing of things) {
var promise = dothing(db, thing);
promises.push(promise);
}
return Promise.all(promises);
}
function dothing(db, thing) {
return new Promise(function(resolve, reject) {
var tx = db.transaction('svgs');
var store = tx.objectStore('svgs');
var request = store.get(thing);
request.onsuccess = () => resolve(request.result);
});
}
function foo() {
var things = getthings();
connect().then(function(db) {
return dothings(db, things);
});
}
Related
For a Manifest 3 browser extension I need to implement local storage logic. This is because there is a lot of code that are shared between different projects that uses localStorage. To change and test all this code on different platforms will be quite a big job.
So I am trying to make a proxy of an object that implement the normal functions of the localStorage object. In the extension I need to use async functions like chrome.storage.local.get.
This gives me a lot of problems with the Promise logic in different ways as getItem return a promise (not intended) or I get runtime errors like "await is only valid in async functions and the top level bodies of modules" etc.
The code below is one such try:
var localStorageTarget = {};
localStorageTarget.getItem = async function(keyname)
{
const internalGetItem = async () =>
{
return new Promise((resolve, reject) =>
{
chrome.storage.local.get([keyname], function (result)
{
if (result[keyname] === undefined)
{
reject();
}
else
{
resolve(result[keyname]);
}
});
});
}
return await internalGetItem();
};
localStorageTarget.setItem = function(keyname, keyvalue)
{
chrome.storage.local.set({keyname: keyvalue});
return true;
};
localStorageTarget.removeItem = function(keyname)
{
chrome.storage.local.remove([keyname]);
return true; // deleteProperty need this
};
const localStorage = new Proxy(localStorageTarget,
{
get: function(obj, name)
{
//console.log('Read request to ' + name + ' property with ' + obj[name] + ' value');
return obj.getItem(name);
},
set: function(obj, name, value)
{
//console.log('Write request to ' + name + ' property with ' + value + ' value');
return obj.setItem(name, value);
},
deleteProperty(obj, name)
{
//console.log('Delete request to ' + name + ' property with ' + obj[name] + ' value');
return obj.removeItem(name);
}
});
localStorage['qqqq'] = 'test2';
console.log(localStorage.getItem('qqqq'));
console.log(localStorage['qqqq']);
delete localStorage['qqqq'];
In advance thank you for for any hint or help
/Benny
You're not going to get around the fact that if the getter returns a Promise, you have to await that promise at the top level:
console.log(await localStorage['qqqq']);
Since chrome.storage.local.get() returns a promise if you don't pass a callback, you can simplify your code:
localStorageTarget.getItem = function(keyname) {
return chrome.storage.local.get([ keyname ]);
}
And lastly, I think that you want expand keyname into a property name:
chrome.storage.local.set({ [ keyname ]: keyvalue });
I guess this is more of a question regarding how to use Promises correctly, which i don't grok:
According to this site (https://ourcodeworld.com/articles/read/405/how-to-convert-pdf-to-text-extract-text-from-pdf-with-javascript), we extract text from a page this way:
// assume pdf file has been loaded
function getPageText(pageNum, PDFDocumentInstance) {
// Return a Promise that is solved once the text of the page is retrieven
return new Promise(function (resolve, reject) {
PDFDocumentInstance.getPage(pageNum).then(function (pdfPage) {
// The main trick to obtain the text of the PDF page, use the getTextContent method
pdfPage.getTextContent().then(function (textContent) {
var textItems = textContent.items;
var finalString = "";
// Concatenate the string of the item to the final string
for (var i = 0; i < textItems.length; i++) {
var item = textItems[i];
finalString += item.str + " ";
}
// Solve promise with the text retrieven from the page
resolve(finalString);
});
});
});
}
I want to search for a certain string through all the pages till i find the page with that string. i tried the obviously wrong way of calling the above function in a for loop, but didn't know how to end when the string was found.
Thanks for the assist!
here's a lame-ish attempt. it's recursive (wish it wasn't), and although it finds the text and gets to the resolve() call, i have no idea where execution goes from there, because it doesn't log to console as i had hoped:
function findText() {
var textToFind = document.getElementById('textToFind').value;
findIt( 1, textToFind ).then( function( pageIndex ) {
// the line below never gets called. i expected the resolve() method
// further down to come here.
console.log( 'Found ' + textToFind + ' on page ' + pageIndex );
},
function(reason) {
console.log(reason);
});
}
function findIt( pageIndex, textToFind ) {
return new Promise( function( resolve, reject ) {
if ( pageIndex > pdfObject.numPages-1 ) {
reject("Couldn't find " + textToFind);
}
getPageText( pageIndex ).then( function( pageText ) {
if ( pageText.indexOf(textToFind) === -1 ) {
findIt( pageIndex+1, textToFind );
}
else {
resolve(pageIndex); // in the debugger, i get here
}
});
});
}
I need to change the text and style of the "Get next" button to "Loading...",
Synchronously retrieve a random number of record IDs from a "server" and Asynchronously retrieve the corresponding records from the "server", only proceeding when all records have been received.
Sort the records in date order, oldest first and at the end reset the button to its original state
The code is as follows
let loading = true;
const buttonHandler = function () {
loading = !loading;
toggleButton(loading);
getRecords();
};
const btn = document.getElementById('get-records');
btn.addEventListener('click', buttonHandler);
function toggleButton(loaded) {
btn.innerHTML = loaded ? 'Loading...' : 'Get next';
btn.classList.toggle('button-not-loading');
btn.classList.toggle('button-loading');
}
function getRecords() {
// getting the IDs of the records to fetch is a synchronous operation
// you don't need to change this call, it should return the IDs
const ids = Server.getIds();
const allTheRecords = [];
// getting each corresponding record is an async operation
ids.forEach(function (recordId) {
Server.getRecord(recordId, function (error, data) {
// if the fetch is unsuccessful the callback function is invoked with the error only
// if the fetch is successful the callback is invoked with error variable set to null,
// and data variable will hold the response (i.e. the record you wanted to retrieve)
if (error) {
console.log(error);
} else {
error = null;
allTheRecords.push(data);
}
});
// you can get a SINGLE record by calling Server.getRecord(recordId, callbackFunction)
// callbackFunction takes 2 parameters, error and data
// invocation as follows
// you need to make sure the list is not rendered until we have the records...
//but need to allow for any fetch errors or app will hang
// i.e. a record you request might not exist - how would you allow for this?
// when you have the records, call processRecords as follows
processRecords(allTheRecords);
});
}
function processRecords(records) {
toggleButton(true);
const sortedRecords = sortRecords(records);
let html = '';
let tr;
sortedRecords.forEach(function (index, value, array) {
tr = '';
tr +=
'<tr>' +
'<td>' + value.date + '</td>' +
'<td>' + value.name + '</td>' +
'<td>' + value.natInsNumber + '</td>' +
'<td>' + value.hoursWorked + '</td>' +
'<td>' + value.hourlyRate + '</td>' +
'<td>' + (value.hoursWorked * value.hourlyRate) + '</td>' +
'</tr>';
html += tr;
});
document.getElementById('results-body').innerHTML = html;
addTotals(sortedRecords);
}
function sortRecords(records) {
let sorted = records.sort(function (a, b) {
return new Date(a.date) - new Date(b.date);
});
// sort results in date order, most recent last
return sorted;
}
function addTotals(records) {
let hours = 0;
let paid = 0;
records.forEach(function (value, index) {
hours += value.hoursWorked;
paid += (value.hoursWorked * value.hourlyRate);
});
document.getElementById('totals-annot').innerHTML = 'TOTALS';
document.getElementById('totals-hours').innerHTML = hours;
document.getElementById('totals-paid').innerHTML = paid;
}
there is no question there, but ill give a vague pseudo code answer which should be enough to point you in the right direction.
Keyword = Promise.
const loadRecordIds = () => {
return new Promise((resolve, reject) => {
jQuery.get('http://localhost/recordIds').then((data) => {
// do something with the data ... e.g parse/validate
resolve(data);
});
});
};
const loadRecords = (recordIds) => {
return new Promise((resolve, reject) => {
jQuery.get('http://localhost/records?recordIds='+recordIds).then((data) => {
// check the data for errors etc
resolve(data);
});
});
};
const toggleButton = () => {
// toggle your button styles
};
// and you use the functions in sequence using .then() or async keyword(if you have a preprocessor or dont care about old browsers)
loadRecordIds().then((recordIds) => {
// now you have your recordIds loaded
toggleButton();
loadRecords(recordIds).then((records) => {
// now you have your records available for further processing
});
});
// with async await keywords you could do the same like this.
try {
const recordIds = await loadRecordIds();
toggleButton();
const records = await loadRecords(recordIds);
} catch (error) {
// handle errors
}
If you dont know what promises are, google them.
// ok, ill throw in a quick sample of an async code that runs in "sync" using promises.
step1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// time has run out now, and its time for the second step
// calling "resolve" will call the "then" function and allows the code to continue
// whatever you pass in as the argument for resolve() will be a parameter in the "then()" function callback.
resolve('3000 seconds has passed, time to continue.');
}, 3000);
});
};
step2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('2000 seconds has passed, time to continue.');
}, 2000);
});
};
step1().then((message) => {
console.log(message);
step2().then((message) => {
console.log(message);
setTimeout(() => {
console.log('and now the script is done...all in sequence');
}, 2000);
});
});
/*
this will output
3000 seconds has passed, time to continue.
2000 seconds has passed, time to continue.
and now the script is done...all in sequence
*/
I am facing following issue:
I am calling in foreach cycle following browse function. When the rb.wsc.browse(symbol) is called the program do some WebSocket request and when the message is returned the event is emmited. The problem is that I always get the same browseData even when I know that the event is emited with different one. I think that this is closure issue, but I don't know how to solve it.
function browse(rb, symbol, callback) {
var result = function(wsc, browseData) {
wsc.off('browse', result);
wsc.off('failed', result);
var err = null;
if (wsc.errno < 0) {
err = new Error("Browsing symbol " + symbol + " failed!");
err.status = wsc.errno;
} else {
saveBrowseData(rb, browseData);
}
callback(err, symbol);
};
// Register temporary listeners
rb.wsc.on('browse', result);
rb.wsc.on('failed', result);
// Browse symbol
rb.wsc.browse(symbol);
}
RexBrowser.prototype.refresh = function() {
var that = this;
var browseRequestNumber = 1;
var browseResult = function(err, symbol) {
browseRequestNumber--;
var item = that.getSymbol(symbol);
_.each(item.children, function(child) {
if (child.browse) {
browseRequestNumber++;
debug("Browsing: " + child.cstring);
browse(that,child.cstring, browseResult);
}
});
if (browseRequestNumber === 0) {
that.emit('refresh', that);
}
};
// Start recursive browsing
browse(this,'$', browseResult);
};-
You could try using a IIFE:
} else {
function(rbInner, browseDataInner){
saveBrowseData(rbInner, browseDataInner);
}(rb, browseData);
}
This makes sure the variables used by / in saveBrowseData have the values they have when the function is called.
the problem here is it never goes into the else statement; I already tried creating a flag to check when it goes into the if and changing but it didn't work
var oUser = {};
// This is to add Name to JS
if (!oUser.name) {
oUser.name = prompt("Enter Name: ") // This is to add Name to JS
localStorage.name = oUser.name
// Now the time
oUser.date = new Date().toUTCString();
localStorage.date = oUser.date;
} else {
var msgDis = document.getElementById('msgDisplay');
msgDis.innerHTML = "Hi " + localStorage.name + " Welcome back!" + " -->Date: " + localStorage.date;
}
oUser.name is undefined, so !oUser.name will always pass. You're creating oUser as an empty Object (var oUser = {};), then checking a data member you never defined.
You should be checking if the localStorage is set:
// Declare oUser in the global scope, as an empty object
var oUser = {};
// check for browser support of localStorage
if(typeof(localStorage) == 'undefined') {
// Check failed, alert user
alert('Your browser does not support the localStorage method!');
} else {
// wrapping this in a try...catch block, incase cookies are disabled
try {
// Attempt to pull oUser (by key) from localStorage, failure results
// in oUser being an empty object.
oUser = JSON.parse(localStorage.getItem('oUser'))||{};
// Now check if oUser.name is NOT set
if(!oUser.name) {
// prompt user for a name
oUser.name = prompt("Enter Name: ");
// insert current date
oUser.date = (new Date()).toUTCString();
// save oUser in localStorage, stringified
localStorage.setItem('oUser',JSON.stringify(oUser));
} else {
// oUser.name was set, welcome them back
var msgDis = document.getElementById("msgDisplay");
msgDisplay.innerHTML = "Hi " + oUser.name + " Welcome back! -->Date: " + oUser.date;
}
} catch(e) {
// Cookies are disabled, which threw an error, alert the user
alert('To use localStorage, you need to enable cookies.');
}
}
Just a neat example I came up with:
LocalStorage the fun way:
LIVE DEMO
LocalStorage Script:
var LS = {
get : function(id) {
var item=localStorage.getItem(id);
return item?(item.charAt(0)=='{'?JSON.parse(item):item):{};
},
set : function(id, key) {
var k=typeof key=="object"?JSON.stringify(key):key;
return localStorage.setItem(id,k);
},
del : function(id){
return localStorage.removeItem(id);
}
};
Basic usage:
LS.set('SomeItemID', "String"||Object ); // Store a String or even an Object!
LS.get('SomeItemID'); // Get your String or your Object;
LS.del('SomeItemID'); // Deletes that LocalStorage item
In your case you can use this script like:
var oU = LS.get('oUser');
// if oUser exists as a LocalStorage key name, "oU" should now look like:
// oU = {name:"somename", date:"somedate"};
if(!oU.name){
oU.name = prompt("Enter Name: ");
oU.date = new Date().toUTCString();
LS.set('oUser', oU); // Store back the whole object
}else{
var msgDis = document.getElementById('msgDisplay');
msgDis.innerHTML = "Hi " + oU.name + " Welcome back!" + " Date: " + oU.date;
}
if you want to delete that item from your localStorage than just do:
LS.del('oUser');
If you want to add more features / fixes,
here's the script reverse-engeenered:
var LS = {
get : function(id) {
var item=localStorage.getItem(id); // Read LocalStorage(id)
if(item){
if(item.charAt(0)=='{'){ // If is String has "{" (is an JSON)
return JSON.parse(item); // Parse to get the Object
}else{
return item; // else return that string
}
}else{
return {}; // return an empty object
}
},
set : function(id, key) {
var k;
if(typeof key=="object"){ // If we're about to store an Object
k = JSON.stringify(key); // transform it to String
}else{
k = key; // else store whatever is key
}
return localStorage.setItem(id,k); // Send to LocalStorage
},
del : function(id){
return localStorage.removeItem(id);// Delete LocalStorage(id)
}
};