I have the following function which resizes images.
The promise should resolve in onload part of my code, however it doesn't for some reason...
import Pica from 'pica';
const pica = new Pica();
export default ({ src, size }) =>
new Promise((resolve, reject) => {
const [width, height] = size.split('x');
const from = new Image();
const to = document.createElement('canvas');
const options = {
width,
height,
};
from.onload = async () => {
try {
const canvas = await pica.resize(from, to, options);
resolve(canvas);
} catch (e) {
reject(e);
}
};
from.src = src;
});
I use this function with to get an array of different-sized versions of one image. I'm using Promise.all() to achieve this.
Here's my code where I'm calling this function:
...
const img = ...
sizes.map(size => {
promisesArray.push(resizeImage({ img, size }));
});
Promise.all(promisesArray).then(data => console.log(data))
I've tried to console.log(resizeImage({ img, size })), and got promise with a pending status which does not resolve.
I've also had an assumption that it was due to Pica. So I tried to simplify the function, but it didn't work either:
// no extra code
export default ({ src, size }) =>
new Promise((resolve, reject) => {
const from = new Image();
from.onload = () => {
// what's wrong here?
resolve('hello');
};
from.src = src;
});
In addition to listing for onload you should always add a listener for onerror since it's possible that the Image will fail to load.
If you add a console.log into the onerror callback I suspect you will see that you've passed in an invalid src to one or more of your calls and the image is simply failing to load.
As an example demonstrating how to wire up the onerror callback (ignoring your resizing code which appears unrelated to your issues):
const loadImg = (src) => new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = (err) => reject(err);
img.src = src;
});
// This should succeed
loadImg('data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7').then(
() => console.log('Image loaded!'),
() => console.log('Image failed!'),
);
// This will fail
loadImg('invalid-url').then(
() => console.log("How did that load?"),
() => console.log("That wasn't an image!"),
);
Related
I want a function which prompts the user to select an image. It should then wait for an image to be selected and then return that image. I want to achieve this without creating a new input element and attaching a new eventlistener every time (atleast not without removing the old ones first) because I expect this button to be used A LOT!
What I have right now is successfully prompting the user can getting the image. But I am not returning it so I can use it however I want after the prompt_for_image() function call. Please help me D:
I added a console log button for testing purposes so you can see the image is being selected propperly.
const images = [];
const file_input = document.createElement('input');
file_input.type = 'file';
file_input.accept = 'image/*';
file_input.addEventListener('change', () => {
const reader = new FileReader();
reader.onload = () => images.push(reader.result);
reader.readAsDataURL(file_input.files[0]);
});
function prompt_for_image() {
file_input.value = null;
file_input.click();
}
document.getElementById('btn_add_new_image').addEventListener('click', async () => {
await prompt_for_image();
});
document.getElementById('btn_log_images').addEventListener('click', () => { console.log(images) });
<button type='button' id='btn_add_new_image'>ADD NEW IMAGE</button>
<button type='button' id='btn_log_images'>console log image array</button>
You'll need to properly promisify FileReader, then promisify the addEventListener. This is easiest done by using the once parameter:
function readFileAsDataURL(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
function waitForNextEvent(element, type) {
return new Promise(resolve => {
element.addEventListener(type, resolve, {once: true});
});
}
Now you can do
const images = [];
const fileInput = Object.assign(document.createElement('input'), {
type: 'file',
accept: 'image/*',
});
async function promptForImage() {
const promise = waitForNextEvent(fileInput, 'change');
fileInput.value = null;
fileInput.click();
await promise;
return fileInput.files[0];
}
document.getElementById('btn_add_new_image').addEventListener('click', async () => {
images.push(await readFileAsDataURL(await promptForImage()));
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
});
document.getElementById('btn_log_images').addEventListener('click', () => {
console.log(images);
});
function readFileAsDataURL(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
function waitForNextEvent(element, type) {
return new Promise(resolve => {
element.addEventListener(type, resolve, {once: true});
});
}
<button type='button' id='btn_add_new_image'>ADD NEW IMAGE</button>
<button type='button' id='btn_log_images'>console log image array</button>
I want to create an image uplaoder based on base64 and I want to get results as an array but I got empty! array, I know maybe it's a asynchronous issue, but I don't know how to use async, await in map any idea?
const [array, setArray] = useState([]);
const fileBase64 = (img) => {
let result = [...img];
setUrlImage(img);
result && result.map(function (img){
let fileReader = new FileReader();
fileReader.readAsDataURL(img);
fileReader.onloadend = async () => {
let res = await fileReader.result;
setArray([...array, res])
};
})
console.log(array)
}
const handleImage = (e) => {
let image = [...e.target.files];
fileBase64(image);
}
<input type="file" multiple={true} onChange={handleImage}/>
Due to this asynchronous nature, state is being set i.e. push before data urls are set in array.
And that's the reason your your array return empty.
To fix it, you can use create Promise which gets resolved after load event of each file. And use Promise.all which would be resolved after each Promise has resolved and then use setArray:
fileBase64 = (img) => {
return new Promise((resolve, reject) => {
let fileReader = new FileReader();
fileReader.onerror = reject
fileReader.onload = function () {
resolve(fileReader.result)
}
fileReader.readAsDataURL(img)
})
}
handleImage = (e) => {
let image = e.target.files;
Promise.all(Array.from(image).map(this.readAsDataURL))
.then((urls) => {
setArray(urls)
})
.catch((error) => {
console.error(error)
})
}
I tried to create an image object by uploading an image file of 170MB Chrome Browser.
However, the error as below is occurring.
enter image description here
What's the cause?
Is there a size constraint on creating an image object?
const loadFromFile = (file: File) => {
return new Promise((resolve, reject) => {
const reader = new CustomFileReader()
reader.onload = () => resolve(reader.result)
reader.onerror = (e) => reject(e)
reader.readAsDataURL(file)
})
}
const createImage = (dataUrl) => {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => resolve(img)
img.onerror = (error) => { // generate Error Event
console.log('image load error', e)
reject(e)
}
img.src = dataUrl
})
}
const load = async (file: File) => {
const dataUrl = await loadFromFile(file) // successful
const image = await createImage(dataUrl) // error
}
This is the image used for the test: https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/La_crucifixi%C3%B3n%2C_by_Juan_de_Flandes%2C_from_Prado_in_Google_Earth.jpg/2560px-La_crucifixi%C3%B3n%2C_by_Juan_de_Flandes%2C_from_Prado_in_Google_Earth.jpg
I have seen couple of examples here and based on those examples I have prepared my solution. But still I am getting Promise
__proto__: Promise
[[PromiseStatus]]: "pending
[[PromiseValue]]: undefined
I am trying to store my base 64 data into my base64 variable. Please let me know what's wrong with my code and a working solution.
Below is my try:
import React, { Component } from 'react'
import { DropzoneArea, DropzoneDialog } from 'material-ui-dropzone'
import Button from '#material-ui/core/Button';
async function getBaseData(selectedFile) {
let base64;
var fileToLoad = selectedFile[0];
base64 = await getBase64(fileToLoad);
return base64; //This returns me PromiseStatus Pending
}
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
return Promise.resolve(reader.result)
});
}
export class ImageUploader extends Component {
constructor(props) {
super(props);
this.state = {
files: [],
image: null,
};
}
handleImageChange(files) {
const { imageCallback } = this.props
imageCallback(getBaseData(files)); // By this callback i am trying to pass my base64 string to other component
}
render() {
return (
<DropzoneArea
acceptedFiles={['image/*']}
filesLimit={1}
maxFileSize={10000000}
//showPreviews={false}
onChange={this.handleImageChange.bind(this)}
/>
)
}
}
your problem is how you are managing your Promise. you dont need to return anything from a Promise as the documentation says or it will resolve inmediatly.
in this case when you do:
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
return Promise.resolve(reader.result) // <--- this is wrong.
});
}
it will resolve to Promise.resolve(reader.result) which is undefined, because maybe it didn't finish, maybe and just maybe you could get an accurate result but it is just luck that the reader actually resolves before that return(race condition)
basically in order to make it work, you need to use the resolver and the rejecter of the promise so it can resolve/reject.
EDIT: I noticed that you are also calling the reader function before setting the callbacks, then you are reading the file, adding the callbacks and then the callbacks are not called.
just change your code to this:
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
//set the callbacks before reading the object
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
reader.readAsDataURL(file);
});
}
The problem is getBaseData is an async function but you still need to wait for it to resolve.
Try "awaiting" for it as well like this:
handleImageChange(files) {
const { imageCallback } = this.props;
async function wrap(){
const result = await getBaseData(files); //result should be a value here and not promise
imageCallback(result);
}
wrap(); // call the async function which will await the getBaseData and use your callback
}
There are two problems here: the creation of the Promise is incorrect, and you don't handle the resolved Promise as you should. There is no need for any async or await like other answers suggest.
The Promise creation:
function getBaseData(selectedFiles) {
var fileToLoad = selectedFiles[0];
return getBase64(fileToLoad);
}
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
reader.readAsDataURL(file);
});
}
Resolving the Promise:
handleImageChange(files) {
const { imageCallback } = this.props
getBaseData(files).then(imageCallback);
}
The function here creates a bunch of files but does not write to them. How ever, if I remove the Promise.all in the end and don't resolve the function at all it DOES write the data to the files. It doesn't matter what I try to write, I can comment out everything and just write 'hello world' and it still won't write anything. The question is simple, why?
const writeSmilFiles = (smilInfo) => {
return new Promise ((resolve, reject) => {
const p1 = smilPartOne();
const p2 = smilPartTwo();
let promises = dataTypes.new.camera_set.map(instance => {
return new Promise((resolve, reject) => {
const smilEntries = smilInfo.filter(smil => smil.BroadcastAndKlippTupleId == instance.BroadcastAndKlippTupleId && smil.CameraId == instance.CameraId);
try {
const fileName = `${__dirname}/newSmilFiles/${smilEntries[0].Smil}`;
const file = fs.createWriteStream(fileName);
file.write(p1);
smilEntries.forEach(entry => {
const smilEntry = smilSwitch(entry.Filename, entry.Bitrate, entry.Width, entry.Height);
file.write(smilEntry);
console.log(smilEntry);
file.write('\n');
});
file.write(p2);
file.end();
resolve(`Smil written.`);
} catch (ex) {
reject(ex);
}
});
});
Promise.all(promises).then(msg => {
resolve(msg);
});
});
};
Resolve when the stream has actually finished:
file.on("finish", () => resolve(`Smil written.`));