Handle specific image response by javascript - javascript

<img src="/a.jpg" onerror="fetch(\'/a.jpg\')
.then(code => console.log(code === 499
? 'image will be available in a moment'
: 'image not found'))">
Is it possible to do this without firing two HTTP requests (one by img.src and one by fetch function)?
My use case is I want to fire a polling loop (which I have already implemented, just skipped it there for simplicity) that will retry loading the image if it is still being prepared on server (the loop will of course fire more HTTP requests, but that's OK), but if the image actually does not exist, just show "image not found".
The server can be implemented for example this way:
if an image exists and has a thumbnail ready, return an image response
if an image exists but thumbnail is not ready yet, return specific HTTP code (499)
Compatibility with modern browsers & IE 11 is enough for me.

Finally found the solution myself - load the image using XHR and display it using BLOB API.
This solution provides all what I wanted:
fires only single HTTP request to get an {image|error code},
does not need additional user permissions (like implementation with webRequest hook),
does not pollute DOM with extra long base64 urls,
seems compatible with modern browsers and even IE10.
var myImage = document.querySelector('img');
var myRequest = new XMLHttpRequest();
myRequest.open('GET', 'http://placekitten.com/123/456', true);
myRequest.responseType = 'blob';
myRequest.onreadystatechange = () => {
if (myRequest.readyState !== 4) {
return;
}
if (myRequest.status === 200) {
var blob = myRequest.response;
var objectURL = URL.createObjectURL(blob);
// this is the trick - generates url like
// blob:http://localhost/adb50c88-9468-40d9-8b0b-1f6ec8bb5a32
myImage.src = objectURL;
} else if (myRequest.status === 499) {
console.log('... waiting for thumbnail');
retryAfter5s(); // implementation of retry loop is not important there
} else {
console.log('Image not found');
}
};
myRequest.send();

Related

Chrome ERR_INSUFFICIENT_RESOURCES workaround

I have a JS library that is responsible to perform the download of JPEG images for the client. All of this is done asynchronously. In some cases, the count of images is really large... Around 5000 images. In this case, the Chrome browser issues the "ERR_INSUFFICIENT_RESOURCES" error for the ajax request.
Each request must be done individually, there is no option to pack the images on the server-side.
What are my options here? How can I find a workaround for this problem? The download works fine in Firefox...
Attached code of the actual download:
function loadFileAndDecrypt(fileId, key, type, length, callback, obj) {
var step = 100 / length;
eventBus.$emit('updateProgressText', "downloadingFiles");
var req = new dh.crypto.HttpRequest();
req.setAesKey(key);
let dataUrl;
if (type == "study") {
dataUrl = "/v1/images/";
}else {
dataUrl = "/v1/dicoms/";
}
var url = axios.defaults.baseURL + dataUrl + fileId;
req.open("GET", url, true);
req.setRequestHeader("Authorization", authHeader().Authorization+"")
req.setRequestHeader("Accept", "application/octet-stream, application/json, text/plain, */*");
req.responseType = "arraybuffer";
req.onload = function() {
console.log(downloadStep);
downloadStep += step;
eventBus.$emit('updatePb', Math.ceil(downloadStep));
var data = req.response;
obj.push(data);
counter ++;
//last one
if (counter == length) {
callback(obj);
}
};
req.send();
}
The error means your code is overloading your memory (most likely, or the quota of pending requests was exhausted). Instead of sending all the data from the backend, make your frontend request for 5000 individual images instead and control the requests flow. regardless, downloading 5000 images is bad. You should pack them up for downloading. If you mean fetching the images, then loading images from the frontend through static or dynamic links is much more logical ;)
Create a class:
Which accepts the file-Id (image that needs to be downloaded) as an argument
Which can perform the HTTP API request
Which can store the result of the request
Create an array of objects from this class using how many ever file-Ids that needs to be downloaded.
Store the array in a RequestManager which can start and manage the downloads:
can batch the downloads, say it fires 5 requests from the array and waits for them to finish before starting the next batch
can stop the downloads on multiple failures
manipulate batch size depending on the available bandwidth
stops download on auth expiry and resumes on auth refresh
offers to retry the previously failed downloads

For in loop is showing the img tag instead of the actualy Giphy gif

I'm trying to make a giphy clone and I want to display six of the trending gifs at the moment. However, when I run the code it seems to work fine in the sense of being able to grab the image source from the response data but the actual gif is not being displayed.
I tried using some of the different url and mp4 links provided in the response data but it always ends up displaying just the image tag.
function getTrending() {
// Create AJAX request to get the trending gifs
// Create the new XHR object
let xhr = new XMLHttpRequest();
// Call the open function with a GET-type request, url, and set async to true
xhr.open('GET', 'http://api.giphy.com/v1/gifs/trending?&api_key=<MyApiKey>&limit=6', true);
// Call the onload function
xhr.onload = function() {
// Check if the server status is 200
if(this.status === 200) {
// Return server response as an object using JSON.parse
let trendingResponse = JSON.parse(this.responseText);
// Create for in loop to insert the trending gifs into the gif container div
for (i in trendingResponse.data) {
gifsContainer.append("<img src='"+ trendingResponse.data[i].images.original.url+"' />")
}
console.log(trendingResponse.data[1]);
}
}
That is because when you use append(), you are actually appending actual text instead of an element/node to your gifsContainer:
The ParentNode.append() method inserts a set of Node objects or DOMString objects after the last child of the ParentNode. DOMString objects are inserted as equivalent Text nodes.
You should construct the image element by using new Image() and then append it instead:
for (i in trendingResponse.data) {
const image = new Image();
image.src = trendingResponse.data[i].images.original.url;
gifsContainer.append(image);
}
If you're more comfortable using document.createElement(), that's also possible:
for (i in trendingResponse.data) {
const image = document.createElement('img');
image.src = trendingResponse.data[i].images.original.url;
gifsContainer.append(image);
}

How to duplicate img tag with src that should not be cached without an HTTP request?

So I have an <img> tag on my page.
<img id="the_image" src="/imageServlet">
Let's assume that the /imageServlet is responding with a few HTTP response headers that indicate that the browser should never cache this image. The reason, in this case, is that the image may contain sensitive employee information and there are laws in certain countries that the browser should not (without user consent) save the image on the client's hard drive in case the machine is stolen. I believe the response header is Cache-Control: no-cache
All I really want to do is duplicate this image tag, but I do not want another HTTP request to be made for this image.
My application is a single page, using javascript I dynamically these image tags - but now I would like to display this same image in more than one place. For example the image could be a thumbnail version of a user's profile picture. In one place I display the thumbnail as a small list, and then I display the same image in a popup when you click that user.
Every time I create a new <img> tag with this src, the browser is making a fresh call to the server to retrieve the image. I don't care if the user presses "refresh" on the browser and a new HTTP request for that image should load - but within a single page load, shouldn't the Image data be available in memory, so couldn't I reuse this?
How can I get around this? This greatly impacts the performance of the page, where images are reused and displayed in multiple places, and they need to reload every time (even though user has not navigated to any other page or refresh browser).
Here is some code that I wrote just now with an idea where any time the application wants to load an image it would call "loadImage" which would eventually call the callback with some HTML element that it can freely use. The idea would be this central function would create the Image but it would make sure that for any unique src only one single HTTP request would be made - regardless if the HTTP response headers contain Cache-Control: no-cache or not.
(function(){
var HANDLERS = {};
/**
* I want to keep around a cached "Image" then the "callback" will be called with a
* duplicate somehow - whatever comes back from here can be inserted somewhere, but
* hopefully no additional HTTP requests needs to be made!!
* #param {Image} img
* #return {Image} A copy of the incoming img
*/
function duplicateImage(img) {
// Does not work!! D'oh, is this even possible to achieve??
return img.cloneNode(true);
}
/**
* Users can call this to load the image. The callback is called when the image is ready.
* In case the image fails the callback will be given an object that contains a success flag.
*
* Example:
* loadImage('/imageServlet?xxxx', function(result) {
* if (result.success) { $(body).append(result.image); }
* else { console.log("The image failed to load!") }
* });
*
* #param {string} src The src for the image
* #param {function({success:boolean, image:Image})} callback Give a callback to be called when ready
*/
window.loadImage = function(src, callback) {
if (!HANDLERS[src]) {
var queue = [];
// This loadImage can be called more than once with the same src
// before the image has successfully loaded. We will queue up any
// callbacks and call them later, then replace this with function
// that will directly invoke the callback later.
HANDLERS[src] = function(callback) {
queue.push(callback);
}
// Create the image here, but do it only once!!
var el = new Image();
// When the image loads, we will keep it around in the el
// variable, but the callback will be called with a copy
el.onload = function() {
el.onload = el.onerror = null;
var call = HANDLERS[src] = function(callback) {
callback({
success: true,
image: duplicateImage(el) // This is where the image is duplicated!
});
}
for (var i=0; i<queue.length; i++) {
call(queue[i]);
}
}
// This is just in case the image fails to load. Call any
// queued callbacks with the success false.
el.onerror = function() {
el.onload = el.onerror = null;
var call = HANDLERS[src] = function(callback) {
callback({success: false});
}
for (var i=0; i<queue.length; i++) {
call(queue[i]);
}
}
el.src = src;
}
HANDLERS[src](callback);
}
})();
Try an ajax call when the page has started to get the image data, and assign that to the "src" of the images you want to "copy" that data to.
$.ajax({
type: "GET",
url: 'some url',
datatype:"image/jpg",
success: function (data) {
$('#img1').attr('src', url.createObjectURL(data));
$('#img2').attr('src', url.createObjectURL(data));
}
});
Because this writes the acutal image into the "src" feild, rather than a link, you can at a later time copy this image from one control to another without more requests to the server very simply:
$('#img3').attr('src', $('#img2').attr('src'));
Non jQuery version
function myAjax() {
var xmlHttp = new XMLHttpRequest();
var url="some url";
xmlHttp.open("GET", url, true);
xmlHttp.setRequestHeader("Content-type", "image/jpg");
xmlHttp.setRequestHeader("Connection", "close");
xmlHttp.onreadystatechange = function() {
if(xmlHttp.readyState == 4 && xmlHttp.status == 200) {
document.getElementById('img1').setAttribute('src', xmlHttp.responseBinary);
}
}
xmlHttp.send();
}
You may want to store file name("src") in an array so that javascript can determine if the image was loaded. You can use canvas to render the image...once the image was loaded into the canvas, you can clone this image using toDataURL()...no need to fetch from server.

Accessing already downloaded data

I need to download a large (>100MB) file of data via XmlHttpRequest. The data is from a third party and I would like to display the content gradually as it gets downloaded.
So I thought the following would work:
var req = new XMLHttpRequest();
req.open( "GET", mirror.url, true );
req.responseType = "arraybuffer";
req.onload = function( oEvent ) {
console.log( "DONE" );
};
var current_offset = 0;
req.addEventListener("progress", function(event) {
if( event.lengthComputable ) {
var percentComplete = Math.round(event.loaded * 100 / event.total);
}
var data = req.response;
// Fails here: req.response is null till load is called
var dataView = new DataView( data );
while( current_offset < dataView.byteLength ) {
// do work
++current_offset;
}
console.log( "OFFSET " + current_offset + " [" + percentComplete + "%]" );
}, false);
try {
req.send( null );
} catch( er ) {
console.log( er );
}
Sadly, according to the spec, .response is not available.
Is there any way to access the already downloaded data without going to such horrible workarounds like using Flash?
EDIT:
Found at least a working non-standard solution for Firefox:
responseType = "moz-chunked-arraybuffer";
See also: WebKit equivalent to Firefox's "moz-chunked-arraybuffer" xhr responseType
One solution would be to download only parts of the file using range requests to only download parts of the file. For more information see the following blog post HTTP Status: 206 Partial Content and Range Requests.
If you have control over the server you could also split up the file on the server side and download chunks of it. This way you would be able to access the response as the different chunks are recieved.
A third approach would be to use WebSockets to download the data.
If you have no control of the server you are downloading from and none of the other options will work, you will probably need to implement a proxy service that will act as an intermediate and allow you to download only part of it.
Have you looked at using a library for this?
I've never used it to load a 100MB file, but PreloadJS is pretty nice for loading all kinds of assets.

Ajax in .NET doesn't want to to load images as it should

Every way I try to obtain what I want with .NET and MSIE 8 desperately fails: Since I coulnd't manage to preload images with "GET", i decided to get them in base64: They came to the client side all right, but there I discovered MSIE8 can't manage base64 over 32Kb, so I'm down again.
So I came back to the classic method I found several times on the web, and there it goes:
var img = new Image();
var ajax = new ActiveXObject('Microsoft.XMLHTTP');
ajax.open("GET", url, false)
ajax.send(null);
var res = ajax.status;
if (res == 200) // succès
{
// this program crashes on next line as soon as 'url' points a jpg file
var tx = rajax.responseText;
// this program crashes on previous line except if 'url' points a text file
img.src = tx;
}
So what can I do to be able to load my image this way ? thanks for your help.
You're misunderstanding the src property.
src takes a URL, not an actual image.
You don't need any AJAX.

Categories