I am using pdfjs to read a pdf file and get its pages as images. After all the images are loaded, I need to call an ajax to send and get details from the server. The code for iterating pages in the pdf has been taken from here: https://ourcodeworld.com/articles/read/405/how-to-convert-pdf-to-text-extract-text-from-pdf-with-javascript
I am having problem writing the syntax for the promise that will call the ajax function after all the required details mentioned above has been fetched.
Here is my code:
getDataUrlsAndSizesFromPdf(file).then(proceedAndCheckOnServer(file));
const getDataUrlsAndSizesFromPdf = function(file) {
PDFJS.disableWorker = true;
fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
return new Promise(function(resolve, reject) {
fileReader.onload = function(ev) {
PDFJS.getDocument(fileReader.result).then(function (pdf) {
var pdfDocument = pdf;
var pagesPromises = [];
for (var i = 0; i < pdf.pdfInfo.numPages; i++) {
var pageNum = i + 1;
pagesPromises.push(getImageUrl(pageNum, pdfDocument));
}
Promise.all(pagesPromises).then(function () {
console.log(pdfPagesInfo);
resolve();
}, function () {
console.log('failed');
reject();
});
}, function (reason) {
console.error(reason);
});
}
});
}
function getImageUrl() {
return new Promise(function (resolve, reject) {
PDFDocumentInstance.getPage(pageNum).then(function (pdfPage) {
var scale = 1;
var viewport = pdfPage.getViewport(scale);
var canvas = document.getElementById('dummy-canvas');
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
var task = pdfPage.render({canvasContext: context, viewport: viewport})
task.promise.then(function(){
var sizesArr = {
height : viewport.height,
width : viewport.width
}
pdfPagesInfo.sizes[pageNum.toString()] = sizesArr
pdfPagesInfo.images[pageNum.toString()] = canvas.toDataURL('image/jpeg');
resolve();
});
});
});
}
function proceedAndCheckOnServer() {
....
}
What I want is "proceedAndCheckOnServer()" to be executed when all the details have been fetched from "getImageUrl()". But presently the execution directly goes to "proceedAndCheckOnServer()" without waiting for the promise from "getDataUrlsAndSizesFromPdf" to be resolved. I am new to javascript promises. Please help me with the syntax.
You are calling your function instead of using a callback function.
proceedAndCheckOnServer is invoked, and the result of that function is used as the parameter to then.
getDataUrlsAndSizesFromPdf(file).then(proceedAndCheckOnServer(file));
Try one of these:
getDataUrlsAndSizesFromPdf(file).then(()=>proceedAndCheckOnServer(file));
getDataUrlsAndSizesFromPdf(file).then(function(){ proceedAndCheckOnServer(file) });
Or resolve your getDataUrlsAndSizesFromPdf promise with file and use the function without () to pipeline the result.
getDataUrlsAndSizesFromPdf(file).then(proceedAndCheckOnServer);
Related
I've got some code running within a Vue.js component, but I get the following error:
TypeError: Cannot read property 'then' of undefined
I just want to run loadingAnimation() before the filereading, but searching on the internet for this error didn't really help me. What is the best way of doing this?
This is my code:
openFileBrowse() {
var vm = this;
var input = document.getElementById("filebutton");
if (input.files[0].type != "application/vnd.ms-excel"){
alert("You have uploaded a wrong file type. We require a .csv file not a " + input.files[0].type + " file.");
} else {
//Update loader text
vm.updateLoader('parsing csv');
//Start loadscreen
vm.loadingAnimation().then(function () {
//Start reading data
var reader = new FileReader();
var csvData = "";
var jsonData;
var iconv = require('iconv-lite');
reader.onload = function(){
csvData = iconv.decode(reader.result, 'latin1');
jsonData = vm.tsvJSON(csvData);
vm.addFiles(jsonData);
};
reader.onloadend = function(){
//Go to visualization page
router.push({ name: 'Visualization' });
};
reader.readAsText(input.files[0]);
});
}
},
loadingAnimation() {
//Make loading screen visible with animation
var target = document.getElementById('loadBox');
target.classList.remove('hidden')
setTimeout(function () {
target.classList.remove('visuallyhidden');
}, 20);
}
loadingAnimation isn't an async function and doesn't explicitly return a promise, so calling it doesn't give you a promise. In fact, it doesn't have any explicit return value, so calling it will always give you undefined.
If you want it to return a promise that will be fulfilled when the timer callback is fired, you have to code it. For instance:
loadingAnimation() {
return new Promise(resolve => {
//Make loading screen visible with animation
var target = document.getElementById('loadBox');
target.classList.remove('hidden')
setTimeout(function () {
target.classList.remove('visuallyhidden');
resolve();
}, 20);
});
}
I'm having a nightmare getting an async loop to work.
I need to retreive an image as data to do something with BUT all I can return is 'undefined' and it doent wait for that result!!
Simply put I need to retreive the data image and pass it to the next function but cant get it to wait!!
heres the code
function getImages(aID){
async function getFileContentAsBase64(path, callback){
window.resolveLocalFileSystemURL(path, gotFile, fail);
function fail(e) {
alert('Cannot found requested file');
}
function gotFile(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(e) {
var content = this.result;
console.log('content looks as it should:::', content);
callback(content);
};
reader.readAsDataURL(file);
});
}
}
console.log("aData uploadImages:", aData);
console.log("aData.length:", aData.length);
(async () => {
console.log("in sync length:::" + aData.length);
for (let i = 0; i < aData.length; i++) {
console.log("in loop:::")
// toastSuccess("fetching image.--.")
var fileURL = 'filepath/filename.jpg';
console.log(i + "-LOOKING FOR:::", fileURL);
const getImage = await getFileContentAsBase64(fileURL, function(base64Image {//<<<<HERE HERE HERE --THIS IS THE ISSUE
console.log("base64Image LOOKS AS IT SHOULD:::", base64Image);
});
console.log(i + "-IMAGEDATA COMES back as undefined", getImage);//<<<HERE HERE HERE - this is the issue. I need to get this value before the loop continues!!!
console.log(i + "-s::: GOT IMAGE");
// toastSuccess("sending image. .-")
var oParams = {id:1}//,body:getImage
var sendImage = await doSomethingElse(getImage, oParams);
}
})();
}
got it: it needed a promise, also removed the async from "function getFileContentAsBase64(. ." at the top of the page
const getImage = new Promise((resolve, reject) => {
getFileContentAsBase64(sfileURL, function(base64Image){ resolve(base64Image); reject('error on return');})
});
thanks .--. . .- -.-. .
In a previous AngularJS app that I am migrating to Angular 8, I had a function that would download functions by binding them and placing them in an array, waiting to be called using a reduce function. For example:
function stageForDownload() {
$scope.files.forEach(function (file) {
if (file.checked) {
$scope.downloadFunctions.push(downloadFile.bind(null, file));
}
});
}
function downloadStaged() {
$scope.downloadFunctions.reduce(
function (prev, next) {
return prev.then(next);
}, Promise.resolve())
.then( /* do something now that all files are downloaded */ );
}
}
This code would essentially resolve promises in a one by one fashion until the list of functions were empty. As in Angular 8, the structure of promises works in a different way (meaning they use the ECMA 6 implementation now) and I am unsure of how to migrate this code. Furthering my confusion, the HttpClient angular provides now returns an Observable which can be subscribed to; and while rsjx's forkJoin() method seems to support exactly what I want to do, it will not accept a list of bound functions.
I simply just need to know when all of the functions are completed, as they are voids that run an export service method to download a file. So I do not necessarily need to return / subscribe to any data from these methods that are being reduced.
Edit:
There are two more functions involved that I forgot to mention. Here is downloadFile, which is responsible for calling the exportService.
function downloadFile(file) {
var deferred = $q.defer();
$scope.date = formatDate($scope.datepicker.selectedDate);
$scope.fileDate = dateToYMD($scope.datepicker.selectedDate);
exportService.exportData(file.FileNamePrefix + " " + $scope.fileDate + ".xlsx", 'SOME_API_LOCATION' + $scope.date, file).then(
function () {
deferred.resolve();
},
function (error) {
deferred.reject();
notificationService.displayError("Internal Error!");
});
return deferred.promise;
}
And here is the exportService itself:
(function (app) {
'uuse strict';
app.factory('exportService', exportService);
exportService.$inject = ['$q', '$http'];
function exportService($q, $http) {
var service = {
exportData: exportData,
createFilename: createFilename
};
function exportData(filename, url, data) {
var config = {
responseType: 'arraybuffer'
};
return $http.post(url, data, config).then(
function (response) {
var deferred = $q.defer();
var data = response.data;
var status = response.status;
var headers = response.headers();
var octetStreamMime = 'application/octet-stream';
var success = false;
var contentType = headers['content-type'] || octetStreamMime;
try {
// Try using msSaveBlob if supported
var blob = new Blob([data], { type: contentType });
if (navigator.msSaveBlob)
navigator.msSaveBlob(blob, filename);
else {
// Try using other saveBlob implementations, if available
var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;
if (saveBlob === undefined) throw "Not supported";
saveBlob(blob, filename);
}
success = true;
deferred.resolve();
} catch (ex) {
}
if (!success) {
// Get the blob url creator
var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL;
if (urlCreator) {
// Try to use a download link
var link = document.createElement('a');
if ('download' in link) {
// Try to simulate a click
try {
// Prepare a blob URL
var blob = new Blob([data], { type: contentType });
var url = urlCreator.createObjectURL(blob);
link.setAttribute('href', url);
// Set the download attribute (Supported in Chrome 14+ / Firefox 20+)
link.setAttribute("download", filename);
// Simulate clicking the download link
var event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
link.dispatchEvent(event);
success = true;
deferred.resolve();
} catch (ex) {
}
}
if (!success) {
// Fallback to window.location method
try {
var blob = new Blob([data], { type: octetStreamMime });
var url = urlCreator.createObjectURL(blob);
window.location = url;
success = true;
deferred.resolve();
} catch (ex) {
deferred.reject();
}
}
}
}
return deferred.promise;
},
function (error) {
return $q.reject(error);
});
}
}
})(angular.module('app'));
So it appears my problem is not necessarily firing off a dynamically created list of http requests, but rather how to convert promises using $q into ECMA6+ promises.
You can use from to turn Promise into Observable.
const observables: Observable<any>[] = promises.map(promise => from(promise));
After that, you can unleash RxJS. You can use forkJoin to get an Observable of all your promises:
forkJoin(observables).subscribe(files => {
// do things with files
});
The way forkJoin is implemented it will only fire once the observables complete. It will not be an issue here, but if you want to batch requests that do not complete, consider zip.
If you want to do use HttpClient, you would just have a different source of your observables.
const observables: Observable<any>[] = urls.map(url => this.httpClient.get(url));
but the forkJoin would be the same. The key is that forkJoin accepts an array of Observables.
Problem:
I am trying to do image preloading and found this awesome solution which I have tried to adapt.I took the code and wrapped it in a function which returns a promise, but i noticed that the done() handler was getting called before all the images were loaded, what am I doing wrong?
Code:
function preload(args) {
var $defer = $.Deferred();
var preload = args;
var promises = [];
for (var i = 0; i < preload.length; i++) {
(function(url, promise) {
var img = new Image();
img.onload = function() {
promise.resolve();
console.log("loaded:" + url);
};
console.log("loading:" + url);
img.src = url;
})(preload[i], promises[i] = $.Deferred());
}
$.when.apply($, promises).done(function() {
console.log("All images ready sir!");
$defer.resolve();
});
return $defer;
}
var images = ['https://pbs.twimg.com/media/CbM1w65UcAAKfSJ.jpg', 'https://www.kenrockwell.com/nikon/d600/sample-images/600_0985.JPG', 'https://developer.nvidia.com/sites/default/files/akamai/gameworks/blog/GameWorks_Vulkan_and_OpenGL_Samples/vulkan2.png', 'https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcSKJ1fJHxLQ6unFkHZnHJoT-RfqrBvWMrzhmRFAPUt0VvdvZSDd', 'https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcTyvamruO8NCTeQJXtQaXM8xEQS5P9ANh_npfgZpv-7x8ISvzX5zg'];
preload(images).done(function() {
console.log("OK, begin business process..");
});
Console Output:
JSFIDDLE:
https://jsfiddle.net/sajjansarkar/f5k94n2r/
Swap these two lines
promise.resolve();
console.log("loaded:" + url);
The promise is resolving which triggers the rest of the resolve chain. Then the log message is added.
I'm trying to make this test work, but I couldn't get my head around how to write a test with FileReader. This is my code
function Uploader(file) {
this.file = file;
}
Uploader.prototype = (function() {
function upload_file(file, file_contents) {
var file_data = new FormData()
file_data.append('filename', file.name)
file_data.append('mimetype', file.type)
file_data.append('data', file_contents)
file_data.append('size', file.size)
$.ajax({
url: "/upload/file",
type: "POST",
data: file_contents,
contentType: file.type,
success: function(){
// $("#thumbnail").attr("src", "/upload/thumbnail");
},
error: function(){
alert("Failed");
},
xhr: function() {
myXhr = $.ajaxSettings.xhr();
if(myXhr.upload){
myXhr.upload.addEventListener('progress',showProgress, false);
} else {
console.log("Upload progress is not supported.");
}
return myXhr;
}
});
}
return {
upload : function() {
var self = this,
reader = new FileReader(),
file_content = {};
reader.onload = function(e) {
file_content = e.target.result.split(',')[1];
upload_file(self.file, file_content);
}
}
};
})();
And this is my test
describe("Uploader", function() {
it("should upload a file successfully", function() {
spyOn($, "ajax");
var fakeFile = {};
var uploader = new Uploader(fakeFile);
uploader.upload();
expect($.ajax.mostRecentCall.args[0]["url"]).toEqual("/upload/file");
})
});
But it never gets to reader.onload.
The problem here is the use of reader.onload which is hard to test. You could use reader.addEventListener instead so you can spy on the global FileReader object and return a mock:
eventListener = jasmine.createSpy();
spyOn(window, "FileReader").andReturn({
addEventListener: eventListener
})
then you can fire the onload callback by yourself:
expect(eventListener.mostRecentCall.args[0]).toEqual('load');
eventListener.mostRecentCall.args[1]({
target:{
result:'the result you wanna test'
}
})
This syntax changed in 2.0. Code below gives an example based on Andreas Köberle's answer but using the new syntax
// create a mock object, its a function with some inspection methods attached
var eventListener = jasmine.createSpy();
// this is going to be returned when FileReader is instantiated
var dummyFileReader = { addEventListener: eventListener };
// pipe the dummy FileReader to the application when FileReader is called on window
// this works because window.FileReader() is equivalent to new FileReader()
spyOn(window, "FileReader").and.returnValue(dummyFileReader)
// your application will do something like this ..
var reader = new FileReader();
// .. and attach the onload event handler
reader.addEventListener('load', function(e) {
// obviously this wouldnt be in your app - but it demonstrates that this is the
// function called by the last line - onloadHandler(event);
expect(e.target.result).toEqual('url');
// jasmine async callback
done();
});
// if addEventListener was called on the spy then mostRecent() will be an object.
// if not it will be null so careful with that. the args array contains the
// arguments that addEventListener was called with. in our case arg[0] is the event name ..
expect(eventListener.calls.mostRecent().args[0]).toEqual('load');
// .. and arg[1] is the event handler function
var onloadHandler = eventListener.calls.mostRecent().args[1];
// which means we can make a dummy event object ..
var event = { target : { result : 'url' } };
// .. and call the applications event handler with our test data as if the user had
// chosen a file via the picker
onloadHandler(event);
I also faced similar problem and was able to achieve it without use of addeventlistener. I had used onloadend, so below is what I did.
My ts file had below code:-
let reader = new FileReader();
reader.onloadend = function() {
let dataUrl = reader.result;
// Some working here
};
reader.readAsDataURL(blob);
My spec file (test) case code :-
let mockFileReader = {
result:'',
readAsDataURL:(blobInput)=> {
console.log('readAsDataURL');
},
onloadend:()=> {
console.log('onloadend');
}
};
spyOn<any>(window, 'FileReader').and.returnValue(mockFileReader);
spyOn<any>(mockFileReader, 'readAsDataURL').and.callFake((blobInput)=> {
// debug your running application and assign to "encodedString" whatever
//value comes actually after using readAsDataURL for e.g.
//"data:*/*;base64,XoteIKsldk......"
mockFileReader.result = encodedString;
mockFileReader.onloadend();
});
This way you have mocked the FileReader object and returned a fake call to your own "readAsDataURL". And thus now when your actual code calls "reasAsDataURL" your fake function is called in which you are assigning an encoded string in "result" and calling "onloadend" function which you had already assigned a functionality in your code (.ts) file. And hence it gets called with expected result.
Hope it helps.
I think the best way is to use the real FileReader (don't mock it), and pass in a real File or Blob. This improves your test coverage and makes your tests less brittle.
If your tests don't run in IE, you can use the File constructor, e.g.
const fakeFile = new File(["some contents"], "file.txt", {type: "text/plain"});
If you need to be compatible with IE, you can construct a Blob and make it look like a file:
const fakeFile = new Blob(["some contents"]);
fakeFile.name = "file.txt";
fakeFile.type = "text/plain";
The FileReader can read either of these objects so there is no need to mock it.
i found easiest for myself to do next.
mock blob file
run reader.onload while in test environment.
as result - i do not mock Filereader
// CONTROLLER
$scope.handleFile = function (e) {
var f = e[0];
$scope.myFile = {
name: "",
size: "",
base64: ""
};
var reader = new FileReader();
reader.onload = function (e) {
try {
var buffer = e.target.result;
$scope.myFile = {
name: f.name,
size: f.size,
base64: XLSX.arrayBufferToBase64(buffer)
};
$scope.$apply();
} catch (error) {
$scope.error = "ERROR!";
$scope.$apply();
}
};
reader.readAsArrayBuffer(f);
//run in test env
if ( typeof jasmine == 'object') {reader.onload(e)}
}
//JASMINE TEST
it('handleFile 0', function () {
var fileContentsEncodedInHex = ["\x45\x6e\x63\x6f\x64\x65\x49\x6e\x48\x65\x78\x42\x65\x63\x61\x75\x73\x65\x42\x69\x6e\x61\x72\x79\x46\x69\x6c\x65\x73\x43\x6f\x6e\x74\x61\x69\x6e\x55\x6e\x70\x72\x69\x6e\x74\x61\x62\x6c\x65\x43\x68\x61\x72\x61\x63\x74\x65\x72\x73"];
var blob = new Blob(fileContentsEncodedInHex);
blob.type = 'application/zip';
blob.name = 'name';
blob.size = 11111;
var e = {0: blob, target: {result: {}}};
$scope.handleFile(e);
expect($scope.error ).toEqual("");
});
I struggled to figure out how to test onloadend when it gets called from readAsDataURL.
Here is a dump of what I ended up with.
Production code:
loadFileDataIntoChargeback(tempFileList) {
var fileNamesAndData = [];
for (var i = 0, f; f = tempFileList[i]; i++) {
let theFile = tempFileList[i];
var reader = new FileReader();
reader.onloadend = ((theFile) => {
return (fileData) => {
var insertionIndex = this.chargeback.fileList.length;
this.chargeback.fileList.push({ FileName: theFile.name, Data: fileData.target.result, FileType: theFile.type });
this.loadFilePreviews(theFile, insertionIndex);
}
})(f);
reader.readAsDataURL(f);
}
this.fileInputPath = "";
}
Test code:
describe('when the files are loaded into the chargeback', () => {
it('loads file previews', () => {
let mockFileReader = {
target: { result: '' },
readAsDataURL: (blobInput) => {},
onloadend: () => {}
};
spyOn(chargeback, "loadFilePreviews");
spyOn(window, 'FileReader').and.returnValue(mockFileReader);
spyOn(mockFileReader, 'readAsDataURL').and.callFake((blobInput) => {
mockFileReader.onloadend({ target: { result: "data:image/jpeg;base64,/9j/4QAYRXh" } });
});
var readFileList = chargeback.getArrayFromFileInput([getImageFile1()]);
chargeback.loadFileDataIntoChargeback(readFileList);
expect(chargeback.loadFilePreviews).toHaveBeenCalled();
});
});