FileReader is not being fired on a web worker - javascript

I have the below function that convert pdfs to images, the function is within a web worker.
For some reason the fileReader.onload is not being fired, the filePdf is correct and is on the right format. Any idea?
const processFile = async (filePdf, post) => {
let PDFJS
if (!PDFJS) {
PDFJS = await import('pdfjs-dist/build/pdf.js')
}
if (!filePdf) return
const fileReader = new FileReader()
console.log(filePdf)
let pages
try {
fileReader.onload = async () => {
const pdf = await PDFJS.getDocument(fileReader.result).promise
pages = await pdfToImageMap(pdf)
}
} catch (e) {
console.log({e})
}
fileReader.readAsArrayBuffer(filePdf)
return post({type: 'done'})
}
filePdf:

Try to change your logic.
At the moment you are trying to wait for the onload, which will work. So the try block succeeds. Then you return your post function. So you've run the file reader, but didn't wait for it load and returned with the post function.
Instead wait for the fileReader to load by awaiting a promise wrapped around the load function. And inside the Promise call fileReader.readAsArrayBuffer(filePdf) to make sure that the onload function is called. In the onload function use your try / catch block to use your PDFJS framework.
Also, don't waste any values stored in variables. If the pages value is something you need, then use it and return it somehow. Otherwise don't store the value and discard it.
Try the snippet below and see if it works.
const processFile = async (filePdf, post) => {
const PDFJS = await import('pdfjs-dist/build/pdf.js')
if (!filePdf) return
console.log(filePdf)
const fileReader = new FileReader()
const pages = await new Promise(resolve => {
fileReader.onload = async () => {
try {
const pdf = await PDFJS.getDocument(fileReader.result).promise
const pages = await pdfToImageMap(pdf)
resolve(pages)
} catch (e) {
console.log({e})
}
}
fileReader.readAsArrayBuffer(filePdf)
})
return post({type: 'done', pages})
}

Related

How to use Clipboard API to write image to clipboard in Safari

The following code (adapted from here) successfully writes an image file to the clipboard upon a button click in Chrome:
document.getElementById('copy-button').addEventListener('click', async () => {
try {
const data = await fetch('image.png')
const blob = await data.blob()
await navigator.clipboard.write(
[new ClipboardItem({[blob.type]: blob})]
)
console.log('success')
} catch (err) {
console.log(`${err.name}: ${err.message}`)
}
})
(Similar code also works with chaining the promises with .then() or copying the contents of a <canvas> using .toBlob() with a callback function)
However, this fails in Safari, throwing a NotAllowedError. I suspect this is something to do with the asynchronous making of the blob causing Safari think that the call to write() is 'outside the scope of a user gesture (such as "click" or "touch" event handlers)' as described here, since control is released from the event handler during the await portions.
For example, the following code pre-loads the blob into a global variable when the script first runs, and the call to write() does not need to wait for any other async code to finish executing:
let imageBlob
(async function () {
const data = await fetch('image.png')
const blob = await data.blob()
imageBlob = blob
console.log('Image loaded into memory')
})()
document.getElementById('image-button-preload').addEventListener('click', () => {
const clipItem = new ClipboardItem({[imageBlob.type]: imageBlob})
navigator.clipboard.write([clipItem]).then( () => {
console.log('success')
}, (reason) => {
console.log(reason)
})
})
But this is clearly not ideal, especially if the image data is something dynamically created (e.g. in a canvas).
So, the question: How can I generate an image blob and write this to the clipboard upon a user action which Safari/webkit will accept? (Or, is this a bug in Safari/webkit's implementation of the API)
The solution (for safari) is to assign a Promise to the value of the hashmap you pass into ClipboardItem like this:
document.getElementById('copy-button').addEventListener('click', async () => {
try {
const makeImagePromise = async () => {
const data = await fetch('image.png')
return await data.blob()
}
await navigator.clipboard.write(
[new ClipboardItem({[blob.type]: makeImagePromise() })]
)
console.log('success')
} catch (err) {
console.log(`${err.name}: ${err.message}`)
}
})
That way you're calling clipboard.write without awaiting, and Safari will await the promise for you that generates the image.
Note: Other browsers may not support passing a promise to ClipboardItem, so you'll likely want to check if the UserAgent contains Mac or iOS in it before doing this.

Await-ing a javascript event handler

I'm struggling with an event handler in javascript, I'm trying to make things respond correctly with async / await.
The issue in the code below is with the file.on event-- I'm struggling to promisify it correctly. Currently the checkSomething method returns before the file.on handler is finished, and so returnValue is always false.
I could use some advice and best practices.
What's a good way to go ensure returnValue is checked / set in the handler before the value is returned? I've tried putting the await keyword in front of the event handler, and in its callback function but that didn't work, and I'm basically scruffing along at this point and could use some tips. Thanks.
const axios = require('axios')
const fs = require('fs')
/*....*/
let isItChecked = await checkSomething()
async function checkSomething() {
let returnValue = false
const response = await axios.get('https://someUrl.com', {
responseType: 'stream'
})
const file = fs.createWriteStream('./localFile.txt')
response.data.pipe(file)
file.on('finish' () => {
performATask().then((result) => {
if (result == 'good') {
returnValue = true //need to make sure this is evaluated before returning
}
})
})
return returnValue
}
Since the event is only used as a signal for processing to continue, you can promisify and await its receipt before continuing with in-line style code. This also means that result need not be declared in outer scope of a nested fumction (which didn't work anyway due to being set asynchronously):
async function checkSomething() {
const response = await axios.get('https://someUrl.com', {
responseType: 'stream'
});
const file = fs.createWriteStream('./localFile.txt');
response.data.pipe(file);
await new Promise( resolve=> file.on(`finish`, resolve)); // wait here
const result = await performATask();
return result == 'good`;
}
You're close, you just have to return a promise, which resolves after the execution reaches your desired point (where you have the comment).
Other things to keep in mind:
Ensure you only use await from within async functions
Use === instead of ==
End statements with ;s
const axios = require('axios');
const fs = require('fs');
/*....*/
let isItChecked = await checkSomething();
async function checkSomething() {
const response = await axios.get('https://someUrl.com', {responseType: 'stream'});
const file = fs.createWriteStream('./localFile.txt');
response.data.pipe(file);
return new Promise(resolve =>
file.on('finish', () =>
performATask().then(result => resolve(result === 'good'))));
}

Blazor JsInterop Invoke after each promise resolves

Trying to get things working correctly in Blazor Server Side App. I have an Uploader Component but it doesn't InvokeAsync after each promise is resolved on client side. It waits for all Images to load then Invokes the C# method. How would I get it to Invoke the C# method after each image is loaded?
I know JavaScript is single threaded but also tried with web workers and still does the same thing.
Sample repo can be found here
https://dev.azure.com/twinnaz/BlazorUploader
Gif of what's happening.
https://imgur.com/a/aF4AQUf
It should be able to invoke the C# method Async in parallel from javascript file if my thinking is correct.
This issue is related with Blazor and JS. On JS you are not awaiting for GenerateImageData.
You should to use a modern for … ofloop instead, in which await will work as expected:
GetFileInputFiles = async (instance, fileInput) => {
var files = Array.from(fileInput.files);
for (const image of files) {
var imagedata = await readUploadedFileAsText(image);
console.log("sending");
_ = await instance.invokeMethodAsync('GenerateImageData', imagedata);
console.log("sent");
};
};
On Blazor, I suggest to you to rewrite GenerateImageData as :
[JSInvokable]
public async Task GenerateImageData(string data)
{
System.Console.WriteLine( "Receiving" );
ImageBase64.Add(data);
await Task.Delay(1);
StateHasChanged();
System.Console.WriteLine( "Received" );
}
Result:
More detailed info about JS issue: Using async/await with a forEach loop
More detailed info about Blazor issue: Blazor - Display wait or spinner on API call
GeneratePreviewImages = async (dotNet, fileInput) => {
const files = Array.from(fileInput.files);
const imageSrcs = files.map(file => getPreviewImageSrc(file));
loop(imageSrcs, dotNet);
};
const loop = async (arr, dotNet) => {
for await (const src of arr) {
console.log(src);
dotNet.invokeMethodAsync('GenerateImageData', src);
}
};
const getPreviewImageSrc = async (file) => {
return URL.createObjectURL(file);
};

Why await a promise doesn't wait the promise to resolve?

I am trying to learn to use properly async await but I am kind of cofused about it.
In the snippets, I am trying to build an array of object containing the infos I need about the file I am uploading in the component. The problem is that the objects in this.fileInfo are not exactly waiting the promise to return the encoded images, returning this output as I console.log this.fileInfo:
As you can see, the key image is a ZoneAwarePromise whose value is undefined. Can you please help me to fix this?
Function build()
async build(e) {
let files = e.target.files;
this.j = Array.from(files);
this.fileInfo = await this.j.reduce((acc, cur) => [
...acc, {
name: cur.name.replace(/^.*\\/, ""),
sizeunit: this.getSize(cur.size),
extention: cur.name.split(/\.(?=[^\.]+$)/).slice(-1).pop().toString(),
image: this.previewFile(cur)
}
], [])
console.log(await this.fileInfo);
}
Promise
async previewFile(file) {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
return new Promise((res) => {
res(reader.result)
}).then( res => res);
};
}
You are not awaiting anything in this function: async previewFile(file).
Perhaps you assume returning a new Promise somewhere along the lines of your code will make it function as a Promise. In this particular case it will not work, because it is inside a delegate (onload), that will not be executed within the scope of your function previewFile().
You can lose the async modifier, because you can return a Promise instead:
previewFileAsync(file) {
// the async modifier keyword is not necessary,
// because we don't need to await anything.
return new Promise((res) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => res(reader.result);
});
}
When you call this, you can await it inside your loop:
async buildAsync(e) {
let files = e.target.files;
for(let i = 0; i < files.length; i++) {
const file = files[i];
const preview = await previewFileAsync(file);
// Do something with preview here...
}
}
Of course, you can execute a range of promises to allow for some sort of concurrency, but this will help get you started.
I added the Async suffix to your method so a caller knows that this can be awaited. It does not do anything special, but it helps clarify your code. You can use whatever suffix you think is right. I'm just used to the Async suffix.
Edit
Stackblitz example of async logic

File Reader is slow and not set state on React

When converting a. pdf file to Base64, I need to be filled in my state, but for some reason my conversion is slower than my setstate, which when being seted is always empty.
My code
async transformBase64(inputFile) {
return new Promise((resolve, reject) => {
var fileReader = new FileReader();
fileReader.readAsDataURL(inputFile)
if (fileReader.result != undefined){
resolve(this.setState({ Base64: fileReader.result }), () => {});
}else{
reject("Err")
}
});
}
What can I do to solve my problem?
It looks like you're not setting up an onload callback for your fileReader. This is needed to correctly signal back to your application that file data has loaded and is ready, seeing that the FileReader API is asynchronous.
Consider apply the following changes to your code to resolve your problem:
async transformBase64(inputFile) {
return new Promise((resolve, reject) => {
var fileReader = new FileReader();
// If error occurs, reject the promise
fileReader.onerror = () => {
reject("Err")
}
// Define an onload handler that's called when file loaded
fileReader.onload = () => {
// File data loaded, so proceed to call setState
if (fileReader.result != undefined){
resolve(this.setState({
Base64: fileReader.result
}), () => {});
}else{
reject("Err")
}
}
fileReader.readAsDataURL(inputFile)
});
}

Categories