The answer from @Arun Killu is a good snippet if you know what is going on, but nobody has explained what was the error on the original code. For people using async
and Promise
calls this is error is soo obvious but for people learning or without experience it's not so clear.
Here a simple explanation.
The Bad code
var blob = items[i].getAsFile();
var reader = new FileReader();
reader.onload = function(event){
console.log(event.target.result)
}; // data url!
var source = reader.readAsBinaryString(blob);
Above code is trying to capture a binary string on source
variable, however, FileReader.readAsBinaryString()
returns undefined
. This is because the result will be available whenever the event onload
will be triggered. As we can see, he was trying to console.log
the event.target.result
value, which is a wrong approach.
The Good code
Here is a step by step implementation:
// 1. Create a FileReader instance
const reader = new FileReader()
// 2. Add a handler for the 'onload' event
reader.onload = (e) => {
// 5. Get the result when the 'onload' event is triggered.
const base64data = reader.result
console.log({base64data})
}
// 3. Add a handler for the 'onerror' event
reader.onerror = () => {
console.log('error')
}
// 4. Call 'readAsDataURL' method
reader.readAsDataURL(imageBlob)
As you can see the last step is 5
and it is because it is an asynchronous call.
Here is a working example:
const url = 'https://i.stack.imgur.com/RRuCp.png'
const fetchImage = async url => {
const response = await fetch(url, {mode: 'no-cors',})
const blob = await response.blob()
return blob
}
const loadImage = async () => {
const imageBlob = await fetchImage(url)
const reader = new FileReader()
reader.onload = () => {
const base64data = reader.result
console.log({base64data})
}
reader.onerror = () => {
console.log('error')
}
reader.readAsDataURL(imageBlob)
}
loadImage()
Teo, what means asynchronous?
Well young Padawan, asynchronous means that we don't know when the result will be ready, it can be different in each system and depends on how heavy or complex is the process and also it can find some errors that will not produce any result at all.
So if a process is asynchronous is a good practice to encapsulate it using an async
method and returning a Promise
like this:
const blobToBase64 = async blob => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result)
reader.onerror = err => reject(err)
reader.readAsDataURL(blob)
})
}
Ah okay Teo, but what is a Promise?
Good question my young fella. A Promise
object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. In other words, will tell us if the result is ready and will give us its value, otherwise will return an error.
Above code shows a function blobToBase64
that will return a Promise
This means that this function will return reader.result
when it ready.
How can we integrate it into our code?
Super easy, just replace all the FileReader with the function blobToBase64
defined above and call it like this imageBase64 = await blobToBase64(imageBlob)
Check this snippet:
const url = 'https://i.stack.imgur.com/RRuCp.png'
const fetchImage = async url => {
const response = await fetch(url, {
mode: 'no-cors',
})
const blob = await response.blob()
return blob
}
const blobToBase64 = async blob => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result)
reader.onerror = err => reject(err)
reader.readAsDataURL(blob)
})
}
const loadImage = async() => {
const imageBlob = await fetchImage(url)
const imageBase64 = await blobToBase64(imageBlob)
console.log({imageBase64})
}
loadImage()
btoa
to convert bytes directly to base64; not sure what's up with the intermediate data urls in the answers. – Explanatory