return a response from an async call in a standard for loop - javascript

After reading How do I return the response from an asynchronous call? by Felix Kling, I am still confused about how I can return a value from an asynchronous callback.
My goal: convert a static image to base64 once and store that image in indexDB until indexDB throws some kind of storage error.
I am using this async idb npm module
// init the idb store
const initIDB = async () => {
const db = await openDB('db', 1, {
upgrade(db) {
db.createObjectStore('tempStore', { keyPath: 'id', autoIncrement: true });
},
});
const tx = db.transaction('tempStore', 'readwrite');
await overloadIDB(tx.store);
await tx.done;
return true;
};
// random number generator
const getRandomArbitrary = (min, max) => Math.random() * (max - min) + min;
// function will overload the idb
const overloadIDB = async (store) => {
const imgurl = "someLocalImage.png";
const promises = [];
return toDataURL(imgurl, async (s) => {
for (let i = 0; i < 10; i++) {
if (i > 0 && i % 100 === 0) console.log('A set done');
try {
const num = Math.round(getRandomArbitrary(1, 1000000));
const data = {
id: num,
img: s,
};
store.add(data);
} catch (e) {
console.log(e.toString());
console.dir(e);
break;
}
}
console.log('Done');
});
};
// convert image to base64
const toDataURL = (url, callback) => {
const xhr = new XMLHttpRequest();
xhr.onload = () => {
const reader = new FileReader();
reader.onloadend = () => {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
};
Ideally, I would like to return the value from the toDataURL's callback function and use that result in the for loop but I always get undefined which makes sense due to asynchronous behaviour.
The above code fails to execute the transaction store.add(data) multiple times and fails when i = 0.
I have tried wrapping toDataURL with a new Promise(resolve, reject) like so
const toDataURL = (url, callback) => new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.onload = () => {
const reader = new FileReader();
reader.onloadend = () => {
resolve(callback(reader.result));
};
reader.readAsDataURL(xhr.response);
};
xhr.send();
});
and then using Promise.all to resolve an array of stores like so
const overloadIDB = async (store) => {
const imgurl = 'someLocalImage.png';
const promises = [];
return toDataURL(imgurl, async (s) => {
console.log('s :', s);
for (let i = 0; i < 10; i++) {
if (i > 0 && i % 100 === 0) console.log('A set done');
try {
const num = Math.round(getRandomArbitrary(1, 1000000));
const data = {
id: num,
img: s,
};
promises.push(store.add(data));
} catch (e) {
console.log(e.toString());
console.dir(e);
break;
}
}
await Promise.all(promises);
console.log('Done');
});
};
but returns an error Failed to execute 'add' on 'IDBObjectStore': The transaction has finished.
At this point I think I my approach is flawed but I am not sure how I can fix it. Can anyone point to some solution please?

You cannot perform async operations in the middle of indexedDB operations. Perform your fetch entirely, then connect, create a transaction, and store the result.

Related

return true in nested function part of new Image() .onload to verify image exist from url?

How can I return true when function is run inside function part of new Image() .onload, in order to verify if a url is a valid image?
var valid = false;
checkImage('https://example.com/image.png')
console.log(valid) //always false at first run
function checkImage(url) {
var image = new Image();
image.onload = function () {
if (this.width > 0) {
valid = true;
}
}
image.onerror = function() {
valid = false;
}
image.src = url;
}
I also tried setting a global variable which doesn't work,Or any other way to return true / false back via checkImage(url) ?
Got this initial solution from https://stackoverflow.com/a/55880263/8719001
(async () => {
let valid = await checkImage('https://example.com/image.png')
console.log(valid)
})();
async function checkImage(url) {
return new Promise(resolve=>{
const image = new Image()
image.onload = () => resolve(!!image.width)
image.onerror = () => resolve(false)
image.src = url
})
}
Following your code example you'll need to wrap your result in a Promise, which is an object made for "returning a result later":
function checkImage(url) {
return new Promise((resolve, reject) => {
var image = new Image();
image.onload = function () {
if (this.width > 0) {
resolve()
} else {
reject()
}
}
image.onerror = reject
image.src = url;
})
}
const valid = await checkImage('https://example.com/image.png')
Alternatively, a simpler way of doing this would be to use fetch if your only goal is to check for the file's existence (and not necessarily checking whether it works as an image):
const exists = await fetch(url, {method: 'HEAD'})
.then(response => response.status === 200)

Issue with preloading assets to browser

I have a Nodejs App running on background and I have this code in frontend ( here it's simplified for you ) to preload videos , audios , etc ... to blobs like this:
function preloadOne(url, done) {
const xhr = new XMLHttpRequest();
xhrs.push(xhr);
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onprogress = event => {
if (!event.lengthComputable) return false;
let item = this.getItemByUrl(event.target.responseURL);
item.completion = parseInt((event.loaded / event.total) * 100);
item.downloaded = event.loaded;
item.total = event.total;
this.updateProgressBar(item);
};
xhr.onload = event => {
let type = event.target.response.type;
let blob = new Blob([event.target.response], {
type: type
});
let url = URL.createObjectURL(blob);
let responseURL = event.target.responseURL;
let item = this.getItemByUrl(responseURL);
item.blobUrl = url;
item.fileName = responseURL.substring(responseURL.lastIndexOf('/') + 1);
item.type = type;
item.size = blob.size;
done(item);
};
xhr.onerror = event => {}
xhr.send();
}
Everything is going fine until I reach 32mb transferred then the transfer gets really slow and for the next 10 mb to load I need more time than what I needed to reach 30MBs!!??
Sometimes and not all the times I get this error for loading a video too:
Failed to load resource: net::ERR_HTTP2_PROTOCOL_ERROR
Here you can see the end of the fast part of the preloading where suddenly everything gets slow:
Any suggestions why this is happening and how to fix this is a good point to start and debug this and it's really appreciated ...
For me it seems something get's full after 30 MBs :)
UPDATE: it a more complete version of the code to have a look if you need it:
async function Preloading() {
preloadedPercent = 0;
setTimeout(() => startBlink(), 1500);
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof definer === 'function' && definer.amd ? definer(factory) :
(global.Preload = factory());
}(this, (function () {
'use strict';
function preloadOne(url, done) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onprogress = event => {
if(!event.lengthComputable) return false
let item = this.getItemByUrl(event.target.responseURL);
item.completion = parseInt((event.loaded / event.total) * 100);
item.downloaded = event.loaded;
item.total = event.total;
this.updateProgressBar(item);
};
xhr.onload = event => {
let type = event.target.response.type;
let blob = new Blob([event.target.response], { type: type });
let url = URL.createObjectURL(blob);
let responseURL = event.target.responseURL;
let item = this.getItemByUrl(responseURL);
item.blobUrl = url;
item.fileName = responseURL.substring(responseURL.lastIndexOf('/') + 1);
item.type = type;
item.size = blob.size;
done(item);
};
xhr.onerror = event => {
console.log('Error Happend in Preloading');
reload();
};
xhr.send();
}
function updateProgressBar(item) {
var sumCompletion = 0;
var maxCompletion = this.status.length * 100;
for(var itemStatus of this.status) {
if(itemStatus.completion) {
sumCompletion += itemStatus.completion;
}
}
var totalCompletion = parseInt((sumCompletion / maxCompletion) * 100);
if(!isNaN(totalCompletion)) {
this.onprogress({
progress: totalCompletion,
item: item
});
}
}
function getItemByUrl(rawUrl) {
for (var item of this.status) {
if (item.url == rawUrl) return item
}
}
function fetch(list) {
return new Promise((resolve, reject) => {
this.loaded = list.length;
for (let item of list) {
this.status.push({ url: item });
this.preloadOne(item, item => {
this.onfetched(item);
this.loaded--;
if (this.loaded == 0) {
this.oncomplete(this.status);
resolve(this.status);
}
});
}
})
}
function Preload() {
return {
status: [],
loaded: false,
onprogress: () => {},
oncomplete: () => {},
onfetched: () => {},
fetch,
updateProgressBar,
preloadOne,
getItemByUrl
}
}
return Preload;
})));
const preload = Preload();
preload.fetch(preloadingData.preloadURLs).then(items => {});
preload.oncomplete = items => {
Media = generateMedia(items);
Blobs = generateBlobs(items);
sfx = getSfx();
preloadProgress(100, true);
preloadingData = null;
}
preload.onprogress = event => { preloadProgress(event.progress); }
preload.onfetched = item => {}
}
function preloadProgress(value, done) {
if(value > preloadedPercent || done) {
preloadedPercent = value;
checkRestart = 0;
checkLatency(value);
progressBar(value, done);
}
}

How to lower the workload of Nodejs app server

Using function below I preload the videos, voices, pictures data as blobs to users browser:
Note that this is a simple XMLHttpRequest to get the files and store the blob in the browser of the user and if you rather not to look at the code it's ok please continue reading the question itself:
async function Preloading() {
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof definer === 'function' && definer.amd ? definer(factory) :
(global.Preload = factory());
}(this, (function () {
'use strict';
function preloadOne(url, done) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onprogress = event => {
if(!event.lengthComputable) return false
let item = this.getItemByUrl(event.target.responseURL);
item.completion = parseInt((event.loaded / event.total) * 100);
item.downloaded = event.loaded;
item.total = event.total;
this.updateProgressBar(item);
};
xhr.onload = event => {
let type = event.target.response.type;
let blob = new Blob([event.target.response], { type: type });
let url = URL.createObjectURL(blob);
let responseURL = event.target.responseURL;
let item = this.getItemByUrl(responseURL);
item.blobUrl = url;
item.fileName = responseURL.substring(responseURL.lastIndexOf('/') + 1);
item.type = type;
item.size = blob.size;
done(item);
};
xhr.onerror = event => {
console.log('Error Happend in Preloading');
reload();
};
xhr.send();
}
function updateProgressBar(item) {
var sumCompletion = 0;
var maxCompletion = this.status.length * 100;
for(var itemStatus of this.status) {
if(itemStatus.completion) {
sumCompletion += itemStatus.completion;
}
}
var totalCompletion = parseInt((sumCompletion / maxCompletion) * 100);
if(!isNaN(totalCompletion)) {
this.onprogress({
progress: totalCompletion,
item: item
});
}
}
function getItemByUrl(rawUrl) {
for (var item of this.status) {
if (item.url == rawUrl) return item
}
}
function fetch(list) {
return new Promise((resolve, reject) => {
this.loaded = list.length;
for (let item of list) {
this.status.push({ url: item });
this.preloadOne(item, item => {
this.onfetched(item);
this.loaded--;
if (this.loaded == 0) {
this.oncomplete(this.status);
resolve(this.status);
}
});
}
})
}
function Preload() {
return {
status: [],
loaded: false,
onprogress: () => {},
oncomplete: () => {},
onfetched: () => {},
fetch,
updateProgressBar,
preloadOne,
getItemByUrl
}
}
return Preload;
})));
const preload = Preload();
preload.fetch(preloadingData.preloadURLs).then(items => {});
preload.oncomplete = items => {
Blobs = generateBlobs(items); // HERE WE SAVE BLOBS FOR USERS
}
preload.onprogress = event => { preloadProgress(event.progress); }
preload.onfetched = item => {}
}
The issue is when multiple users try to download their data using the above function, sometimes the function doesn't respond to them and user will miss some of the blobs ( some blobs will not be created if server is busy )
Now I have a straight question:
What if I place my files ( video, voices, pictures, etc..) in a download server, and my Nodejs app running in another so that downloading the files does not impact the application server ? does this fix the issue and lowers the workload of the application's server ?

Execute a new set of fetches only after the earlier set has ran trough

I'm trying to fetch images for a live camera feed web page from an API. The problem is that with just timed fetch request, eventually one of the API responds takes so long that the code errors. The page has multiple camera feeds that are simultaneously refreshing with the loop for all the cameras in the cameraObjects[] array. The image() function should respond with a resolve that would then be collected in to an array promises[].
Put simply I need to run the refreshImages() function when ALL the image() functions called by the loop in updateImages() have been ran. I have just started coding so bare with me...
class camera {
constructor(Uuid,url,username,password) {
this.Uuid = Uuid;
this.url = url;
this.username = username;
this.password = password;
}
image() {
let uuid = this.Uuid
let url = this.url
let username = this.username
let password = this.password
let headers = new Headers();
let authString = `${username}:${password}`;
headers.set('Authorization', 'Basic ' + btoa(authString));
let imageUrl = url + uuid
fetch(imageUrl,{method: 'GET', headers: headers})
.then(response => response.blob())
.then(image => {
console.log(image);
var reader = new FileReader();
reader.readAsDataURL(image);
reader.onloadend = function() {
var base64data = reader.result;
let img = document.getElementById("camera_" + uuid);
img.src = base64data;
return new Promise(function(resolve) {
resolve(promise);
})
}
})
}
}
function updateImages() {
cameraObjects = listOfCameraObjects();
let promises = [];
for(let e = 0; e < cameraObjects.length; e++) {
let promise = new Promise(cameraObjects[e].image())
promises.push(promise)
}
Promise.all(promises)
.then(() => {
refreshImages();
})
}
function refreshImages() {
let currentInterval = getInterval();
refrehInterval = setTimeout(updateImages, currentInterval);
console.log(refrehInterval)
}
There a few things you're doing wrong with Promises -
return new Promise(function(resolve) {
resolve(promise);
})
That's sort of OK though return Promise.resolve(promise) is identical - however in your code, what is promise? not declared anywhere in that scope - also, as the last code in an event handler (onloadend) it is pointless, since returning a value from an event handler is meaningless
let promise = new Promise(cameraObjects[e].image())
that's not how you construct a promise ... the argument to the promise constructor needs to be a function, not the result of calling a function (unless that returns a function, of course, but it doesn't)
I'd suggest you perhaps read some docs about how you construct a Promise, and how you then use them
In the meantime, I believe this code will do what you want
class camera {
constructor(Uuid,url,username,password) {
this.Uuid = Uuid;
this.url = url;
this.username = username;
this.password = password;
}
image() {
const headers = new Headers();
const authString = `${this.username}:${this.password}`;
headers.set('Authorization', 'Basic ' + btoa(authString));
const imageUrl = this.url + this.Uuid;
return fetch(imageUrl, {method: 'GET', headers: headers})
.then(response => response.blob())
.then(image => new Promise((resolve, reject) {
const reader = new FileReader();
reader.readAsDataURL(image);
reader.addEventListener('loadend', () => {
const img = document.getElementById("camera_" + uuid);
img.src = reader.result;
resolve();
});
reader.addEventListener('error', reject);
}));
}
}
function updateImages() {
Promise.all(listOfCameraObjects().map(cam => cam.image()))
.then(refreshImages);
}
function refreshImages() {
let currentInterval = getInterval();
refrehInterval = setTimeout(updateImages, currentInterval);
console.log(refrehInterval)
}

How to get current data size of each objectStore in indexedDB programmatically?

I want to get current data size (Usage in MB/GB) of each objectStore in indexedDB, programmatically.
I think this might be popular question, but I can't find any similar questions/answers in StackOverflow.
Is this possible?
This code takes much time if the number of DB entries are many, but I can get the result by this code.
const countDB = async (db, table) => {
return new Promise((resolve, reject) => {
const tx = db.transaction([table], 'readonly');
const store = tx.objectStore(table);
const cursorReq = store.openCursor();
let count = 0;
let size = 0;
cursorReq.onsuccess = function(e) {
const cursor = cursorReq.result;
if (cursor) {
count++;
size = size + cursor.value.blob.size;
cursor.continue();
}
};
cursorReq.onerror = function(e) {
reject(e);
};
tx.oncomplete = function(e) {
resolve({
count: count,
size: size
});
};
tx.onabort = function(e) {
reject(e);
};
tx.onerror = function(e) {
reject(e);
};
});
};

Categories

Resources