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

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)

Related

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)
}

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

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.

How can I make return works in load function on vue component?

I have vue component like this :
<template>
<section>
...
</section>
</template>
<script>
export default {
...
data() {
return {
allowableTypes: ['jpg', 'jpeg', 'png'],
maximumSize: 4000000
}
},
methods: {
...
onFileChange(e) {
if (this.validate(e.target.files[0])) {
let files = e.target.files,
reader = new FileReader()
// if any values
if (files.length){
reader.onload = (e) => {
this.image = e.target.result
}
reader.readAsDataURL(files[0])
}
}
},
validate(image) {
// validation file type
if (!this.allowableTypes.includes(image.name.split(".").pop().toLowerCase())) {
return false
}
// validation file size
if (image.size > this.maximumSize) {
return false
}
// validation image resolution
let img = new Image()
img.src = window.URL.createObjectURL(image)
let self = this
img.onload = function() {
let width = img.naturalWidth,
height = img.naturalHeight
window.URL.revokeObjectURL(img.src)
if(width != 850 && height != 350) {
return false
}
}
return true
}
}
}
</script>
If user upload image, it will call onFileChange method. Before displaying the image, it will call method validate to validate the image.
I try to validate file size and file type and it works. The problem here is validating the resolution.
From my code, it seems my code is true
But when I try like this:
I upload image with width = 100, height = 100, from the code, should it return `false``.
But when I run my code, it returns true.
Seems return is not working in the img.onload
How can I solve this problem?
A nice way to handle asynchronous validation is by using Promises :
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
If you are targeting Internet Explorer, make sure to use a polyfill such as this one :
https://github.com/stefanpenner/es6-promise
Your code would then look like this :
onFileChange(e) {
let self = this
this.validate(e.target.files[0])
.then(function() {
let files = e.target.files,
reader = new FileReader()
// if any values
if (files.length) {
reader.onload = (e) => {
self.image = e.target.result
}
reader.readAsDataURL(files[0])
}
})
.catch(function() {
// do something in the case where the image is not valid
})
},
validate(image) {
let self = this
return new Promise(function(resolve, reject) {
// validation file type
if (!self.allowableTypes.includes(image.name.split(".").pop().toLowerCase())) {
reject()
}
// validation file size
if (image.size > self.maximumSize) {
reject()
}
// validation image resolution
let img = new Image()
img.src = window.URL.createObjectURL(image)
img.onload = function() {
let width = img.naturalWidth,
height = img.naturalHeight
window.URL.revokeObjectURL(img.src)
if (width != 850 && height != 350) {
reject()
} else {
resolve()
}
}
})
}
If you do not want or cannot use Promises you could use a Callback to achieve the same behaviour :
onFileChange(e) {
let self = this
let validCallback = function() {
let files = e.target.files,
reader = new FileReader()
// if any values
if (files.length) {
reader.onload = (e) => {
self.image = e.target.result
}
reader.readAsDataURL(files[0])
}
}
let unvalidCallback = function() {
// do something in the case where the image is not valid
}
this.validate(e.target.files[0], validCallback, unvalidCallback)
},
validate(image, validCallback, unvalidCallback) {
// validation file type
if (!this.allowableTypes.includes(image.name.split(".").pop().toLowerCase())) {
unvalidCallback()
return
}
// validation file size
if (image.size > this.maximumSize) {
unvalidCallback()
return
}
// validation image resolution
let img = new Image()
img.src = window.URL.createObjectURL(image)
let self = this
img.onload = function() {
let width = img.naturalWidth,
height = img.naturalHeight
window.URL.revokeObjectURL(img.src)
if (width != 850 && height != 350) {
unvalidCallback()
return
} else {
validCallback()
}
}
}
It's onloadend not onload.
Change your code to this:
let self = this;
var reader = new FileReader();
reader.onloadend = () => {
// you logic here (use self, not this)
}

Make ts/js-library promise based

I've implemented this file reader into my project.
I would like to make this return a promise when it finishes with the file reading, but I don't know how to propagate the promise from there.
class MyClass {
constructor() {}
public start(file) {
parseFile(file);
}
private parseFile(file) {
let fileSize = file.size;
let chunkSize = 10000;
let offset = 0;
let self = this;
let readBlock = null;
// How do I get this success function to return a promise to the user?
let success = function() { return new Promise...? };
let onLoadHandler = function(evt) {
if (evt.target.error == null) {
offset += evt.target.result.length;
chunkReadCallback(evt.target.result);
} else {
chunkErrorCallback(evt.target.error);
return;
}
if (offset >= fileSize) {
success(file);
return;
}
readBlock(offset, chunkSize, file);
}
readBlock = function(_offset, length, _file) {
let r = new FileReader();
let blob = _file.slice(_offset, length + _offset);
r.onload = onLoadHandler;
r.readAsText(blob);
}
readBlock(offset, chunkSize, file);
}
}
Today it works like this:
let x = new MyClass();
x.start(file);
And I would like it to be like this instead:
let x = new MyClass();
x.start(file).then(() => { console.log('done') });
Where do I put my return Promise so that the user can handle the promise?
Thanks!
The following should turn readFile into a promise:
private parseFile(file,chunkSize,offset) {
let fileSize = file.size;
let self = this;
readBlock = function (_offset, length, _file) {
return new Promise(
function(resolve,reject){
let r = new FileReader();
let blob = _file.slice(_offset, length + _offset);
//https://developer.mozilla.org/en-US/docs/Web/API/FileReader/onload
r.onload = function(e){
if(e.target.error!==null){
reject(e.target.error);
}
else{
resolve(e.target.result)
}
};
//https://developer.mozilla.org/en-US/docs/Web/API/FileReader/onerror
r.onerror = function(err){
reject(err);
}
r.readAsText(blob);
}
)
}
return readBlock(offset, chunkSize, file);
}
You can have the caller define what the block size is and when to read the next block.
An example how to use this function:
x.parseFile(file,file.size,0)
.then(
function(textData){
console.log(textData);
}
);
//read in chunks of 1000
function readInChunks(file,chunkSize=1000,offset=0,data=""){
return x.parseFile(file,chunkSize,offset)
.then(
function(textData){
if(offset+chunkSize>=file.size){
return data+textData;
}
console.log("get next chunk");
//recursively call itself
return readInChunks(file,chunkSize,offset+chunkSize,data+textData);
}
)
}
//call read in chunks
readInChunks(file,/* optional, defaults to 1000 */500)
.then(
function(textData){
console.log("got data:",textData);
}
)

Check if a image exists with a given URL - JavaScript

I'm trying to check if an image exists given a url using javascript, but my code is not working. Thanks!
Here's my function :
function verifyImageURL(url, callBack) {
var img = new Image();
img.src = url;
img.onload = function () {
callBack(true);
};
img.onerror = function () {
callBack(false);
};
}
And Here's how I call it:
var url = "http://greenstyle.com.br/wp-content/uploads/2012/09/Tucano-imagem-Silvia-Kochen-Wordpress.jpg";
verifyImageURL(url, function (imageExists) {
if (imageExists === true) {
alert("Image Exists");
} else {
alert("Image does not Exist");
}
});
This question hasn't had activity in a long time, but since I saw another recent answer, I thought I would share a solution which fits the asker's example pattern of using a callback, but alternatively returns a promise if no callback argument is provided:
See code in the TypeScript Playground to view types and the overloaded function signature
function loadImage (url, timeoutOrCallback, maybeCallback) {
let timeout;
let callback;
if (typeof timeoutOrCallback === 'number') {
timeout = timeoutOrCallback;
if (typeof maybeCallback === 'function') callback = maybeCallback;
}
else if (typeof timeoutOrCallback === 'function') callback = timeoutOrCallback;
const promise = callback
? undefined
: new Promise(resolve => void (callback = resolve));
const onlyRunOnce = {once: true};
let timerId = 0;
let done = false;
if (typeof timeout === 'number') {
timerId = setTimeout(() => {
done = true;
callback(false);
}, timeout);
}
const img = new Image();
img.addEventListener('load', () => {
if (done) return;
clearTimeout(timerId);
done = true;
callback(true);
}, onlyRunOnce);
img.addEventListener('error', () => {
if (done) return;
clearTimeout(timerId);
done = true;
callback(false);
}, onlyRunOnce);
img.src = url;
return promise;
}
// Usage examples:
function asyncExample (url, logId) {
const timeout = 3e3; // 3s
loadImage(url, timeout).then(imageLoaded => {
console.log(`${logId}: async with timeout:`, imageLoaded)
});
loadImage(url).then(imageLoaded => {
console.log(`${logId}: async:`, imageLoaded)
});
}
function callbackExample (url, logId) {
const timeout = 3e3; // 3s
let cb = imageLoaded => console.log(`${logId}: callback with timeout:`, imageLoaded);
loadImage(url, timeout, cb);
cb = imageLoaded => console.log(`${logId}: callback:`, imageLoaded);
loadImage(url, cb);
}
let url = 'https://i.stack.imgur.com/TxzmG.jpg';
asyncExample(url, 'SO image');
callbackExample(url, 'SO image');
url = 'https://invalid.example/image.jpg';
asyncExample(url, 'invalid image');
callbackExample(url, 'invalid image');
Try this approach
var x = new XMLHttpRequest(); x.open("get", url, true);x.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) {alert('exist');}else{alert('does not exist'};}; x.send();

Categories

Resources