Read data url in for each promise? - javascript

I want to replace in my object a firebase child data image url by a database64 string. It work pretty well but i have issue with async ? how can i wait for all my value inside my foreach ?
return FirebaseRef.child('rav').on('value', snapshot => {
// const userData = snapshot.val() || [];
var obj = {
}
snapshot.forEach(child => {
function toDataURL(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var reader = new FileReader();
reader.onloadend = function () {
callback(reader.result);
}
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
}
toDataURL(child.val().avatar, function (dataUrl) {
// console.log('RESULT:', dataUrl)
obj[child.key] = {
...child.val(),
avatar: dataUrl
}
console.log('obj', obj)
})
});
console.log('obj', obj)
this.updateAvatar({ profile: obj }); // Send to reducer
});
},
}),

Create an empty array before the forEach, have the callback() function return a Promise.resolve(), push the return value (the promise) of callback() into your array, then (after the forEach) use Promise.resolve() with your array to trigger the action you want to call after all the FileReader() calls have completed.

Let me start by stating that you shouldn't define a function inside a loop. It's redefining at every step of the loop. That's not your issue though. Lucky for you I wrote such a function earlier this year. This dataURL was found on wikipedia.
function dataURLtoBlob(dataURL){
var d;
if(typeof dataURL !== 'string' || !((d = dataURL.split(';base64,')) && d.length > 1)){
return false;
}
var s = atob(decodeURIComponent(d[1])), l = s.length, a = new Uint8Array(l);
for(var i=0; i<l; i++){
a[i] = s.charCodeAt(i);
}
return new Blob([a], {type:'image/png'});
}
var url = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDADIiJSwlHzIsKSw4NTI7S31RS0VFS5ltc1p9tZ++u7Kfr6zI4f/zyNT/16yv+v/9////////wfD/////////////2wBDATU4OEtCS5NRUZP/zq/O////////////////////////////////////////////////////////////////////wAARCAAYAEADAREAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAQMAAgQF/8QAJRABAAIBBAEEAgMAAAAAAAAAAQIRAAMSITEEEyJBgTORUWFx/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AOgM52xQDrjvAV5Xv0vfKUALlTQfeBm0HThMNHXkL0Lw/swN5qgA8yT4MCS1OEOJV8mBz9Z05yfW8iSx7p4j+jA1aD6Wj7ZMzstsfvAas4UyRHvjrAkC9KhpLMClQntlqFc2X1gUj4viwVObKrddH9YDoHvuujAEuNV+bLwFS8XxdSr+Cq3Vf+4F5RgQl6ZR2p1eAzU/HX80YBYyJLCuexwJCO2O1bwCRidAfWBSctswbI12GAJT3yiwFR7+MBjGK2g/WAJR3FdF84E2rK5VR0YH/9k=';
console.log(url);
dataURLtoBlob(url).text().then(function(str){
console.log(str);
});
<img src='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDADIiJSwlHzIsKSw4NTI7S31RS0VFS5ltc1p9tZ++u7Kfr6zI4f/zyNT/16yv+v/9////////wfD/////////////2wBDATU4OEtCS5NRUZP/zq/O////////////////////////////////////////////////////////////////////wAARCAAYAEADAREAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAQMAAgQF/8QAJRABAAIBBAEEAgMAAAAAAAAAAQIRAAMSITEEEyJBgTORUWFx/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AOgM52xQDrjvAV5Xv0vfKUALlTQfeBm0HThMNHXkL0Lw/swN5qgA8yT4MCS1OEOJV8mBz9Z05yfW8iSx7p4j+jA1aD6Wj7ZMzstsfvAas4UyRHvjrAkC9KhpLMClQntlqFc2X1gUj4viwVObKrddH9YDoHvuujAEuNV+bLwFS8XxdSr+Cq3Vf+4F5RgQl6ZR2p1eAzU/HX80YBYyJLCuexwJCO2O1bwCRidAfWBSctswbI12GAJT3yiwFR7+MBjGK2g/WAJR3FdF84E2rK5VR0YH/9k='/>
Of course, you should ask yourself why you're doing this when you can store an image in Firebase.
PS
You should be able to send a Blob to your Server using FormDataInstance.append.

Related

Using promise to call Ajax without duplicating code

is this possible? I want to write an ajax function, that I do not want to duplicate it. Pass it different parameter which are locations to different files. Then use the promise to make them into one object. I would possible use the spread operator. is this possible.
var myFuncCalls = 0;
let promiseAjax = new Promise (function ( resolve,reject) {
//possibly use a for look to grab the number of times the loadDoc was called then call the same function and send it to may be an array?
function loadDoc(location) {
myFuncCalls++;
console.log("loadDoc was called :" + myFuncCalls);
var xyz = new XMLHttpRequest();
xyz.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
//console.log(this.responseText)
resolve(this.responseText);
}
};
xyz.open("GET", location, true);
xyz.send();
}
loadDoc("/_js/someitems.json");
loadDoc("/_js/someMoreItems.json");
})
// then grab all that stuff and make one single object using spread operators
promiseAjax.then(function (fromResolve){
// JSON.parse(fromResolve);
var newObj = JSON.parse(fromResolve);
console.log(newObj);
})
with Promise.all and Object.assign,
function loadDoc(location) {
return new Promise((resolve, reject) => {
var xyz = new XMLHttpRequest();
xyz.onreadystatechange = () => {
if (this.readyState == 4 && this.status == 200) {
resolve(JSON.parse(this.responseText));
} else {
// resolving with empty object to avoid breaking other fetch if one failed
resolve({});
}
};
xyz.open("GET", location, true);
xyz.send();
});
}
const loadDocs = (paths) => Promise.all(paths.map(path => loadDoc(path))
.then(results => {
// combine all result into single object
return Object.assign({}, ...results);
}));
// example
loadDocs([
"/_js/someitems.json",
"/_js/someMoreItems.json"
]).then(function(finalCombinedObject) {
// other logic here
});
Use Promise.all() to get the two calls together and so what ever you want with the array of the data you resolved.
function loadDoc(location) {
return new Promise (function ( resolve,reject) {
var xyz = new XMLHttpRequest();
xyz.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
resolve(this.responseText);
}
};
xyz.open("GET", location, true);
xyz.send();
});
}
const urls = ["/_js/someitems.json", "/_js/someMoreItems.json"]
Promise.all(urls.map(url=>loadDoc(url))).then(responses =>
console.log(responses);
)
I think the easiest thing would be to define async functions, which return promises and can be easily passed around and reused.
You can do something like:
async function loadFile(file) {
...
return {...fileJSON};
}
async function loadFiles() {
const file1JSON = await loadFile('file1');
const file2JSON = await loadFile('file2');
return {...file1JSON, ...file2JSON};
}
loadFiles().then((combinedJSON) => {
...
})
These functions can take arguments and be reused like any other function.
This kind of behavior can archived with Promise.all Promise.all white the use of async+await async and the use of more state of the art calls (fetch) makes the code looks cleaner
async function loadAll(docs) {
return Promise.all(docs.map(async doc => {
const result = await fetch('http://example.com/movies.json');
return result.json();
}));
}
(async function() {
const responses = await loadAll(["/_js/someitems.json", "/_js/someMoreItems.json"]);
console.log(responses);
})();
Note: await can only be used from an async function.
Note2: the code is untested
Yes, youcan send the URL, any parameters, even the type of AJAX call (POST, GET, etc), to the method, then use it to build the call. This way, you can reuse the same method to do anything and everything you need to do from your client with a "simple" method call.
All code in this Answer is copied from the below link.
https://medium.com/front-end-weekly/ajax-async-callback-promise-e98f8074ebd7
function makeAjaxCall(url, methodType)
{
var promiseObj = new Promise(function(resolve, reject)
{
var xhr = new XMLHttpRequest();
xhr.open(methodType, url, true);
xhr.send();
xhr.onreadystatechange = function()
{
if (xhr.readyState === 4)
{
if (xhr.status === 200)
{
console.log("xhr done successfully");
var resp = xhr.responseText;
var respJson = JSON.parse(resp);
resolve(respJson);
}
else
{
reject(xhr.status);
console.log("xhr failed");
}
}
else {console.log('xhr processing going on');}
}
console.log("request sent succesfully");
});
return promiseObj;
}
enter code here
document.getElementById('userDetails').addEventListener('click', function()
{
// git hub url to get btford details
var userId = document.getElementById("userId").value;
var URL = "https://api.github.com/users/"+userId;
makeAjaxCall(URL, "GET").then(processUserDetailsResponse, errorHandler);
});
You can even send it the callback method. I also send it a method to use for errors.
function makeAjaxCall(url, methodType, callback)
{
$.ajax(
{
url : url,
method : methodType,
dataType : "json",
success : callback,
error : function (reason, xhr){
console.log("error in processing your request", reason);
}
});
}
// git hub url to get btford details
var URL = "https://api.github.com/users/btford";
makeAjaxCall(URL, "GET", function(respJson)
{
document.getElementById("userid").innerHTML = respJson.login;
document.getElementById("name").innerHTML = respJson.name;
document.getElementById("company").innerHTML = respJson.company;
document.getElementById("blog").innerHTML = respJson.blog;
document.getElementById("location").innerHTML = respJson.location;
});

How to convert a image src from a blob string to data URI

I have a page where the user can paste an image into a content editable div. When I get the image the src returns a string. When I look in debug tools this is what I see:
<img src="blob:http://www.example.com/3955202440-AeFf-4a9e-b82c-cae3822d96d4"/>
How do I convert that to a base 64 string?
Here is the test script, http://jsfiddle.net/bt7BU/824/.
// We start by checking if the browser supports the
// Clipboard object. If not, we need to create a
// contenteditable element that catches all pasted data
if (!window.Clipboard) {
var pasteCatcher = document.createElement("div");
// Firefox allows images to be pasted into contenteditable elements
pasteCatcher.setAttribute("contenteditable", "");
// We can hide the element and append it to the body,
pasteCatcher.style.opacity = 0.5;
document.body.appendChild(pasteCatcher);
// as long as we make sure it is always in focus
pasteCatcher.focus();
document.addEventListener("click", function() { pasteCatcher.focus(); });
}
// Add the paste event listener
window.addEventListener("paste", pasteHandler);
/* Handle paste events */
function pasteHandler(e) {
// We need to check if event.clipboardData is supported (Chrome)
if (e.clipboardData) {
// Get the items from the clipboard
var items = e.clipboardData.items || e.clipboardData.files;
var itemcount = items ? items.length : 0;
pasteArea.value = "items found:"+itemcount;
if (itemcount) {
// Loop through all items, looking for any kind of image
for (var i = 0; i < items.length; i++) {
if (items[i].type.indexOf("image") !== -1) {
// We need to represent the image as a file,
var blob = items[i].getAsFile();
// and use a URL or webkitURL (whichever is available to the browser)
// to create a temporary URL to the object
var URLObj = window.URL || window.webkitURL;
var source = URLObj.createObjectURL(blob);
// The URL can then be used as the source of an image
createImage(source);
}
}
} else {
console.log("no items found. checking input");
// This is a cheap trick to make sure we read the data
// AFTER it has been inserted.
setTimeout(checkInput, 1);
}
// If we can't handle clipboard data directly (Firefox),
// we need to read what was pasted from the contenteditable element
} else {
console.log("checking input");
// This is a cheap trick to make sure we read the data
// AFTER it has been inserted.
setTimeout(checkInput, 1);
}
}
/* Parse the input in the paste catcher element */
function checkInput() {
console.log("check input");
// Store the pasted content in a variable
var child = pasteCatcher.childNodes[0];
// Clear the inner html to make sure we're always
// getting the latest inserted content
//pasteCatcher.innerHTML = "";
//console.log( "clearing catcher");
console.log(child);
if (child) {
// If the user pastes an image, the src attribute
// will represent the image as a base64 encoded string.
if (child.tagName === "IMG") {
createImage(child.src);
reader = new FileReader();
reader.readAsDataURL(child.src);
reader.loadend = function(e) {
console.log(e.target.result);
}
}
}
}
/* Creates a new image from a given source */
function createImage(source) {
var pastedImage = new Image();
pastedImage.onload = function(e) {
//pasteArea.text = pastedImage.src;
console.log(1);
console.log(e);
loadImage.src = e.target.src;
console.log(loadImage.src);
}
pastedImage.src = source;
}
<textarea id="pasteArea" placeholder="Paste Image Here"></textarea>
<img id="loadImage" />
I'm testing this in Safari on Mac.
Since the blobURI is generated automatically by the browser, you can use this, which will download the produced image as a new Blob:
const toDataURL = url => fetch(url)
.then(response => response.blob())
.then(blob => new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onloadend = () => resolve(reader.result)
reader.onerror = reject
reader.readAsDataURL(blob)
}))
And then on your function createImage(source) { you can call it:
toDataURL(source)
.then(dataUrl => {
console.log('RESULT:', dataUrl)
})
This answer is complimentary to #BrunoLM's answer for when you don't have ES6 or you want to read in a different image type.
ES6:
const toDataURL = url => fetch(url)
.then(response => response.blob())
.then(blob => new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onloadend = () => resolve(reader.result)
reader.onerror = reject
reader.readAsDataURL(blob)
}))
Not ES6 (seems to work the same):
const toDataURL = function(url) {
return fetch(url).then(function(response) {
return response.blob();
}).then(function (blob) {
var type = blob.type;
var size = blob.size;
return new Promise(function(resolve, reject) {
const reader = new FileReader();
reader.onerror = reject;
reader.readAsDataURL(blob);
reader.onloadend = function() {
return resolve(reader.result);
}
}
)}
)}
Based on my understanding of ES6 (ES6 to not ES6):
var a = url => fetch(url)
var a = function(url) { return fetch(url) }
var a = function(parameter) { return statement }
var b = (parameter, parameter) => { fetch(param, param) }
var b = function(foo1, foo2) => { return fetch(param, param) }
var c = url = () => resolve(reader.result)
var c = url = function() { return resolve() }
Making a call:
toDataURL(url).then(function(dataUrl) {
console.log("RESULT:" + dataUrl);
});
Note:
The value returned by the above method is of type "image/tiff" when run in Safari on OSX. If you want to specify another type, such as PNG, there more info on that here.

Can't post the javascript array of object

I am trying to post javascript array of object in an ajax call but i get string value "[]". When i try to console.log the array lenght it says zero.
Following is the code i am using
var masterFileArray = []; // where I will store the contents
function readMultipleFiles(evt) {
//Retrieve all the files from the FileList object
var files = evt.target.files;
if (files) {
for (var i = 0, f; f = files[i]; i++) {
var r = new FileReader();
r.onload = (function (f) {
return function (e) {
var contents = e.target.result;
masterFileArray.push({name:f.name, contents: contents, type:f.type, size:f.size}); // storing as object
};
})(f);
r.readAsText(f);
}
console.log(masterFileArray);
new Ajax.Request('fileupload.php', {
method: 'post',
parameters: {files: JSON.stringify(masterFileArray)},
onSuccess: function(transport){
var response = transport.responseText;
console.log(response);
}
});
} else {
alert('Failed to load files');
}
}
document.getElementById('upfiles').addEventListener('change', readMultipleFiles, false);
Thats how it looks like on inspection
What i am doing wrong? Any help would be appreciated, Thank you.
You can post after reading finished,
Here introduced left_loaded_count to get the status of reading.
Try like this.
var masterFileArray = []; // where I will store the contents
var left_loaded_count = 0;
function readMultipleFiles(evt) {
//Retrieve all the files from the FileList object
var files = evt.target.files;
if (files) {
for (var i = 0, f; f = files[i]; i++) {
var r = new FileReader();
r.onload = (function (f) {
return function (e) {
var contents = e.target.result;
masterFileArray.push({name:f.name, contents: contents, type:f.type, size:f.size}); // storing as object
left_loaded_count -= 1;
if(left_loaded_count == 0)
{
console.log(masterFileArray);
new Ajax.Request('fileupload.php', {
method: 'post',
parameters: {files: JSON.stringify(masterFileArray)},
onSuccess: function(transport){
var response = transport.responseText;
console.log(response);
}
});
}
};
})(f);
left_loaded_count += 1;
r.readAsText(f);
}
} else {
alert('Failed to load files');
}
}
document.getElementById('upfiles').addEventListener('change', readMultipleFiles, false);
readAsText() is an asynchronous operation, but you proceed with the AJAX call right away instead of waiting for the read operations to finish. That's why your console.log(masterFileArray) prints an empty array, when it runs none of the operations have finished and the array is still empty.
The best way to solve this is to wrap each file read operation in a promise and then proceed with the AJAX call once all these promises resolve.
Get rid of var masterFileArray = [] and change your code within the if (files) { ... } block to this:
Promise.all(files.map(function(f) {
return new Promise(function(resolve) {
var r = new FileReader();
r.onload = function (e) {
var contents = e.target.result;
resolve({name:f.name, contents: contents, type:f.type, size:f.size}); // resolve promise with object
};
r.readAsText(f);
});
})).then(function(masterFileArray) {
// All promises have resolved and their results have been collected in masterFileArray
console.log(masterFileArray);
new Ajax.Request('fileupload.php', {
method: 'post',
parameters: {files: JSON.stringify(masterFileArray)},
onSuccess: function(transport){
var response = transport.responseText;
console.log(response);
}
);
});

Array.prototype.map.call not working for getElementsByClassName objects

class_name = document.getElementsByClassName('image');
const map = fn => x => Array.prototype.map.call(x, fn);
map(img => {
console.log(img);
img.addEventListener('mouseover', (e) => {
global = e.target.src;
calledsomething(global);
});
})(class_name);
I'm trying to make a simple chrome extension but Array.prototype.map.call doesn't seems to be called. console.log(img) doesn't display anything.
I tested my code using getElementsByTagName('img') instead of getElementsByClassName and it works.
What might be the problem?
EDIT: The whole code:
var isHovered = false;
var global;
const class_name = document.getElementsByClassName('photo_activity_item__img_wrapper');
const map = fn => x => Array.prototype.map.call(x, fn);
console.log(class_name);
map(img => {
img.addEventListener('mouseover', (e) => {
global = e.target.src;
console.log(global);
hoveredBox();
});
img.addEventListener('mouseleave', (e) => {
isHovered = false;
});
})(class_name);
document.addEventListener('keypress', keyDown);
function hoveredBox() {
isHovered = true;
}
function keyDown(event) {
if (!isHovered) return;
var key = event.keyCode;
if (key === 115) {
saveFile(global);
}
}
// Download a file form a url.
function saveFile(url) {
// Get file name from url.
filename = url;
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function () {
var a = document.createElement('a');
a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob
console.log(a.href);
console.log(xhr.response);
a.download = filename; // Set the file name.
a.style.display = 'none';
document.body.appendChild(a);
a.click();
delete a;
};
xhr.open('GET', url);
xhr.send();
}
While the return of the getElementsByClassName() may be an array like object, it's quite possible it isn't a true Array. I ran into this this morning while attempting to call findIndex() on the Element.children object. Turns out that is a NodeList, and not an actual Array. I was able to get around it though, by using the new Array.from() method like this:
const index = Array.from(parent.children).findIndex(item => item.classList.contains('placeholder'));
Try that Array.from() and see if your issues are resolved.

Angular2 : Asynchronous upload array of files

I'm trying to upload an array of files using FileReader which are base64 encoded and stored in an array for further processing. I'm struggling to understand the pattern I need to create in order to ensure all files have been uploaded because I have to wait for the onload event handler to fire. For example;
If I pass an array of files to the following function it will resolve the promise before the files are actually uploaded.
localUploads( event ) : any {
var response = [];
return new Promise( function(resolve, reject) {
//Retrieve all the files from the FileList object
var files = event.target.files;
var response = [];
if (files) {
for (var i=0, f; f=files[i]; i++) {
var r = new FileReader();
r.onload = (function(f) {
return function(e) {
let contents = e.target['result'];
let file = {
name: f.name,
asset: contents,
private: false
};
console.log('we are pushing into the array');
response.push( file );
};
})(f);
}
resolve( response );
}
r.readAsText(f);
});
}
Can anybody please advise a novice?
Many thanks.
It's all about resolving the promise at the right time. Currently you resolve it when the loop is done, but that doesn't mean that all the items have been processed. I haven't used FileReader, but you should be able to do something like this:
localUploads( event ) : any {
var response = [];
return new Promise(function(resolve, reject) {
//Retrieve all the files from the FileList object
var files = event.target.files;
var response = [];
if (files) {
for (var i=0, f; f=files[i]; i++) {
var r = new FileReader();
r.onload = function(e) { // Possible clean-up?
let contents = e.target['result'];
let file = {
name: f.name,
asset: contents,
private: false
};
console.log('we are pushing into the array');
response.push( file );
if(response.length == files.length)
{
// Everything is done. Resolve the promise.
resolve( response );
}
};
// Moved here to be able to access r and f variables
r.readAsText(f);
}
}
});
}
Or the old way using $q.
var response = [];
var dfd = $q.defer();
//Retrieve all the files from the FileList object
var files = event.target.files;
var response = [];
if (files) {
for (var i=0, f; f=files[i]; i++) {
var r = new FileReader();
r.onload = function(e) { // Possible clean-up?
let contents = e.target['result'];
let file = {
name: f.name,
asset: contents,
private: false
};
console.log('we are pushing into the array');
response.push( file );
if(response.length == files.length)
{
// Everything is done. Resolve the promise.
dfd.resolve(response);
}
};
// Moved here to be able to access r and f variables
r.readAsText(f);
}
}
else {
// Possible resolve promise here to?
}
return dfd.promise;
Note that you might also want to handle the possible errors. If one of the files isn't completed sucessfully the promise will never be resolved.
You might need to resolve the promise in onloadend instead of onload. I really cannot figure it out from the docs.
var r = new FileReader();
r.onload = function(e) {
if(response.length == files.length)
{
// Everything is done. Resolve the promise.
dfd.resolve(response);
}
}

Categories

Resources