Pre-load images and get progress - javascript
I have got the following script I use to pre-load images:
export default function PreloadImages(images) {
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
resolve(img);
};
img.onerror = img.onabort = function() {
reject(src);
};
img.src = src;
});
}
return Promise.all(images.map(src => loadImage(src)));
}
I then use this within a Vue component like follows:
PreloadImages(this.images).then(() => (this.imagesPreloaded = true));
I want to be able to get progress from this however within my component so I can display for example 2/50 Images Loaded. How would I go about doing this?
Edit
This is what I ended up with:
PreloadImages.js
export default function PreloadImages(images) {
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
resolve(img);
};
img.onerror = img.onabort = function() {
reject(src);
};
img.src = src;
});
}
return images.map(src => loadImage(src));
}
Within my component:
handlePreload() {
PreloadImages(this.images).forEach(p => p.then(() => {
this.imagesPreloaded++;
}));
},
try this:
function PreloadImages(images) {
let promises = [];
images.map(src => loadImage(src));
function loadImage(src) {
promises.push(new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
resolve(img);
};
img.onerror = img.onabort = function() {
reject(src);
};
img.src = src;
}));
}
Promise.all(promises);
return promises;
}
let images = [];
let counter = 0;
let promises = PreloadImages(images);
promises.forEach(p => p.then(() => {
counter++;
console.log(counter);
}));
This is how you should approach it, don't introduce any extra looping:
var imageSrcList = [
"https://image.flaticon.com/icons/svg/565/565860.svg",
"https://image.flaticon.com/icons/svg/565/565861.svg",
"https://Error.Done.One.Purpose",
"https://image.flaticon.com/icons/svg/565/565855.svg",
];
var imageContainer = document.getElementById("loadedImages");
var loadedCount = 0;
function displayCurrentLoadStatus(){
console.log("Current load progress: ", loadedCount, " of ", imageSrcList.length);
}
function PreloadImages(images) {
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = src;
img.onload = function()
{
++loadedCount;
imageContainer.appendChild(img);
displayCurrentLoadStatus();
resolve(img);
};
img.onerror = img.onabort = function() {
reject(src);
};
});
}
// Note that I removed Promise.all, let's just return a list of promises for the images passed
return images.map((imgSrc, i)=> loadImage(imgSrc).catch((rejectedSrcError=> rejectedSrcError)));
}
// now let's create all promises for each image in images
Promise.all(PreloadImages(imageSrcList)).then( resolvedSrcList =>
{
// Use your resolved Src's here
console.log("Load complete: ",loadedCount, "/",resolvedSrcList.length, " images were loaded successfully.");
});
img{
width: 60px;
height: 60px;
display: inline-block;
}
<div id="loadedImages"></div>
... tldr ...
There will be 2 different basic ideas and 4 solutions for demonstration purpose.
The first two approaches are valid in case one can not or is not allowed to change the return value (a single promise) of PreloadImages as from the OP's originally provided example code.
The 3rd example presents the preferred approach which is to change the return value of PreloadImages to a list of (image preloading) promises, whereas the 4th example is just No.3 that in addition features a real live demo of progress-bar and statistics rendering.
1) Proxy based
One could use a Proxy object that one, in addition to the image source list, does provides to the preloadImages function.
One would use such an object in its set and validate configuration.
Choosing a Proxy based approach comes with following advantages ...
Introducing eventProxy into preloadImages makes this new implementation agnostic to later user cases.
One just needs to implement functionality for an API that provides all necessary information about image loading states in a/the most generic way.
The user specific, but in the preloadImages' scope unknown use case, that later needs to be handled, will be provided as part of the eventProxy.
/*export default */function preloadImages(imageSourceList, eventProxy) {
// - the introduction of an `eventProxy` makes this implementation
// agnostic to later user cases.
// - one just needs to implement the necessary api that provides
// information about image loading states.
// - the user specific, but in this scope unknown use case, that later
// needs to be handled, will be provided as part of the `eventProxy`.
const maxCount = imageSourceList.length;
let successCount = 0;
let failureCount = 0;
function loadImage(src) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = function() {
resolve(image);
++successCount;
eventProxy.event = {
image,
maxCount,
successCount,
failureCount
}
};
image.onerror = image.onabort = function() {
reject(src);
++failureCount;
eventProxy.event = {
image,
maxCount,
successCount,
failureCount
}
};
image.src = src;
});
}
return Promise.all(imageSourceList.map(src => loadImage(src)));
}
function renderImageLoadProgress(evt) {
// the render functionality goes in here ...
console.log('renderImageLoadProgress :: evt :', evt);
}
function isImageLoadType(type) {
return (
type
&& ('image' in type)
&& ('maxCount' in type)
&& ('successCount' in type)
&& ('failureCount' in type)
)
}
const loadEventValidator = {
set: function(obj, key, value) {
let isSuccess = false;
if ((key === 'event') && isImageLoadType(value)) {
obj[key] = value;
isSuccess = true;
// provide render functionality as part of
// the proxy's correct setter environment.
renderImageLoadProgress(value);
}
return isSuccess;
}
}
const loadEventProxy = new Proxy({}, loadEventValidator);
const imageSourceList = [
'https://picsum.photos/id/1011', // loading will fail.
'https://picsum.photos/id/1011/5472/3648',
'https://picsum.photos/id/1012/3973/2639',
'https://picsum.photos/id/1013/4256/2832',
'https://picsum.photos/id/1013', // loading will fail.
'https://picsum.photos/id/1014/6016/4000',
'https://picsum.photos/id/1015/6000/4000',
'https://picsum.photos/id/1016/3844/2563'
];
preloadImages(imageSourceList, loadEventProxy);
.as-console-wrapper { min-height: 100%!important; top: 0; }
2) Event based
In case of not being able making use of proxies, one easily can switch the above approach to EventTarget and CustomEvent, especially since there are polyfills available that also do work in older versions of Internet Explorer. The idea behind this solution and its advantages are the same as with the former Proxy based one ...
/*export default */function preloadImages(imageSourceList, eventTarget) {
// - additionally injecting/providing an `eventTarget` makes this
// implementation agnostic to later user cases.
// - one just needs to dispatch all relevant information via the
// `details` property of a custom event.
// - the user specific, but in this scope unknown use case, will be
// handled from ouside via listening to the custom event's type.
// - there are polyfills for `EventTarget` as well as for `CustomEvent`
// that also do work in older versions of Internet Explorer.
const maxCount = imageSourceList.length;
let successCount = 0;
let failureCount = 0;
function loadImage(src) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = function() {
resolve(image);
++successCount;
eventTarget.dispatchEvent(new CustomEvent("loadstatechange", {
detail: {
image,
maxCount,
successCount,
failureCount
}
}));
};
image.onerror = image.onabort = function() {
reject(src);
++failureCount;
eventTarget.dispatchEvent(new CustomEvent("loadstatechange", {
detail: {
image,
maxCount,
successCount,
failureCount
}
}));
};
image.src = src;
});
}
return Promise.all(imageSourceList.map(src => loadImage(src)));
}
function renderImageLoadProgress(evt) {
// the render functionality goes in here ...
console.log('renderImageLoadProgress :: evt.detail :', evt.detail);
}
const loadEventTarget = new EventTarget();
loadEventTarget.addEventListener('loadstatechange', renderImageLoadProgress);
const imageSourceList = [
'https://picsum.photos/id/1011', // loading will fail.
'https://picsum.photos/id/1011/5472/3648',
'https://picsum.photos/id/1012/3973/2639',
'https://picsum.photos/id/1013/4256/2832',
'https://picsum.photos/id/1013', // loading will fail.
'https://picsum.photos/id/1014/6016/4000',
'https://picsum.photos/id/1015/6000/4000',
'https://picsum.photos/id/1016/3844/2563'
];
preloadImages(imageSourceList, loadEventTarget);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Note
Since the 2 above given approaches did not change the return value/signature of the original implementation of PreloadImages they had to be implemented exactly each in their own way. But both together come with a lot of additional code, the proxy based approach being a little fatter than the event based one.
3) Return Array of Promises
Anyhow, if one is/was just willing to change the return value of the former PreloadImages to an array of image loading promises one could achieve a very clean and lean implementation that might look similar to the next one ...
/*export default */function getAsyncLoadingImageList(imageSourceList) {
const totalCount = imageSourceList.length;
let successCount = 0;
let failureCount = 0;
function loadImage(src) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = function() {
resolve({
image,
counts: {
total: totalCount,
success: ++successCount,
failure: failureCount
},
success: true,
});
};
image.onerror = image.onabort = function() {
reject({
image,
counts: {
total: totalCount,
success: successCount,
failure: ++failureCount
},
success: false,
});
};
image.src = src;
});
}
// return list of *image loading* promises.
return imageSourceList.map(src => loadImage(src));
}
function renderImageLoadProgress(imageLoadData) {
// the render method.
console.log('imageLoadData : ', imageLoadData);
}
const imageSourceList = [
'https://picsum.photos/id/1011', // loading will fail.
'https://picsum.photos/id/1011/5472/3648',
'https://picsum.photos/id/1012/3973/2639',
'https://picsum.photos/id/1013/4256/2832',
'https://picsum.photos/id/1013', // loading will fail.
'https://picsum.photos/id/1014/6016/4000',
'https://picsum.photos/id/1015/6000/4000',
'https://picsum.photos/id/1016/3844/2563'
];
getAsyncLoadingImageList(imageSourceList).forEach(promise =>
promise
.then(renderImageLoadProgress)
.catch(renderImageLoadProgress)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
4) Bonus
In order to present a real progress bar demo, the 3rd example, that already had it all, was taken as is, but now features a full blown render method instead of just logging the image-loading progress-data ...
/*export default */function getAsyncLoadingImageList(imageSourceList) {
const totalCount = imageSourceList.length;
let successCount = 0;
let failureCount = 0;
function loadImage(src) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = function() {
resolve({
image,
counts: {
total: totalCount,
success: ++successCount,
failure: failureCount
},
success: true,
});
};
image.onerror = image.onabort = function() {
reject({
image,
counts: {
total: totalCount,
success: successCount,
failure: ++failureCount
},
success: false,
});
};
image.src = src;
});
}
// return list of *image loading* promises.
return imageSourceList.map(src => loadImage(src));
}
function getImageLoadProgressRenderConfig(progressContainer) {
const elmProgress = progressContainer.querySelector('progress');
const countsContainer = progressContainer.querySelector('.image-counts');
const failuresContainer = progressContainer.querySelector('.load-failures');
const elmImageCount = countsContainer.querySelector('.count');
const elmImageTotal = countsContainer.querySelector('.total');
const elmFailureCount = failuresContainer.querySelector('.failure-count');
const elmCurrentCount = failuresContainer.querySelector('.current-count');
return {
nodes: {
progress: {
display: elmProgress,
count: elmImageCount,
total: elmImageTotal
},
failure: {
display: failuresContainer,
failureCount: elmFailureCount,
currentCount: elmCurrentCount
}
},
classNames: {
failure: 'failures-exist'
}
};
}
function renderImageLoadProgressViaBoundConfig(imageLoadData) {
const imageCounts = imageLoadData.counts;
const imageCountTotal = imageCounts.total;
const imageCountSuccess = imageCounts.success;
const imageCountFailure = imageCounts.failure;
const imageCountCurrent = (imageCountSuccess + imageCountFailure);
const renderConfig = this;
const renderNodes = renderConfig.nodes;
const progressNodes = renderNodes.progress;
const failureNodes = renderNodes.failure;
const classNameFailure = renderConfig.classNames.failure;
const isFailureOnDisplay =
failureNodes.display.classList.contains(classNameFailure);
progressNodes.display.textContent = `${ imageCountCurrent } \/ ${ imageCountTotal }`;
progressNodes.display.value = imageCountCurrent;
progressNodes.count.textContent = imageCountCurrent;
if (!imageLoadData.success) {
if (!isFailureOnDisplay) {
failureNodes.display.classList.add(classNameFailure);
}
failureNodes.failureCount.textContent = imageCountFailure;
} else if (isFailureOnDisplay) {
failureNodes.currentCount.textContent = imageCountCurrent
}
}
function preloadImagesAndRenderLoadProgress(imageSourceList, progressContainer) {
const totalImageCount = imageSourceList.length;
const renderConfig = getImageLoadProgressRenderConfig(progressContainer);
const renderNodes = renderConfig.nodes;
const progressNodes = renderNodes.progress;
const failureNodes = renderNodes.failure;
const renderImageLoadProgress =
renderImageLoadProgressViaBoundConfig.bind(renderConfig);
failureNodes.display.classList.remove(renderConfig.classNames.failure);
failureNodes.failureCount.textContent = 0;
failureNodes.currentCount.textContent = 0;
progressNodes.display.max = totalImageCount;
progressNodes.total.textContent = totalImageCount;
renderImageLoadProgress({
counts: {
success: 0,
failure: 0
},
success: true,
});
getAsyncLoadingImageList(imageSourceList).forEach(promise => promise
.then(renderImageLoadProgress)
.catch(renderImageLoadProgress)
);
}
const imageSourceListWithFailure = [
'https://picsum.photos/id/1011', // loading will fail.
'https://picsum.photos/id/1011/5472/3648',
'https://picsum.photos/id/1012/3973/2639',
'https://picsum.photos/id/1013/4256/2832',
'https://picsum.photos/id/1013', // loading will fail.
'https://picsum.photos/id/1014/6016/4000',
'https://picsum.photos/id/1015/6000/4000',
'https://picsum.photos/id/1016/3844/2563',
'https://picsum.photos/id/1018', // loading will fail.
'https://picsum.photos/id/1018/3914/2935',
'https://picsum.photos/id/1019/5472/3648',
'https://picsum.photos/id/1020/4288/2848',
'https://picsum.photos/id/1021', // loading will fail.
'https://picsum.photos/id/1021/2048/1206',
'https://picsum.photos/id/1022/6000/3376',
'https://picsum.photos/id/1023/3955/2094'
];
const imageSourceListWithoutFailure = [
'https://picsum.photos/id/1039/6945/4635',
'https://picsum.photos/id/1038/3914/5863',
'https://picsum.photos/id/1037/5760/3840',
'https://picsum.photos/id/1036/4608/3072',
'https://picsum.photos/id/1035/5854/3903',
'https://picsum.photos/id/1033/2048/1365',
'https://picsum.photos/id/1032/2880/1800',
'https://picsum.photos/id/1031/5446/3063',
'https://picsum.photos/id/1029/4887/2759',
'https://picsum.photos/id/1028/5184/3456',
'https://picsum.photos/id/1027/2848/4272',
'https://picsum.photos/id/1026/4621/3070'
];
preloadImagesAndRenderLoadProgress(
imageSourceListWithFailure,
document.querySelector('#loading-with-failure')
);
preloadImagesAndRenderLoadProgress(
imageSourceListWithoutFailure,
document.querySelector('#loading-without-failure')
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
.image-loading-progress {
margin: 20px 0;
}
.image-loading-progress,
.image-loading-progress > span,
.image-loading-progress progress {
display: block;
width: 100%;
}
.image-loading-progress > .load-failures {
display: none
}
.image-loading-progress > .load-failures.failures-exist {
display: block
}
<label class="image-loading-progress" id="loading-with-failure">
<progress value="0" max="0">0 / 0</progress>
<span class="image-counts">
...
<span class="count">0</span>
out of
<span class="total">0</span>
images finished loading ...
</span>
<span class="load-failures">
...
<span class="failure-count">0</span>
out of
<span class="current-count">0</span>
images are not available ...
</span>
</label>
<label class="image-loading-progress" id="loading-without-failure">
<progress value="0" max="0">0 / 0</progress>
<span class="image-counts">
...
<span class="count">0</span>
out of
<span class="total">0</span>
images finished loading ...
</span>
<span class="load-failures">
...
<span class="failure-count">0</span>
out of
<span class="current-count">0</span>
images are not available ...
</span>
</label>
Related
Validating image's aspect ratio (width/height) with Yup & formik
I am trying to do the validation using yup for image file, what I found was https://github.com/formium/formik/issues/926 which only validates the size and also the file type. and this one is the current yup validation that I use file: lazy(value => { switch (typeof value) { case 'string': return string().required(errorHandler.requiredFile()); default: return mixed() .required(errorHandler.requiredFile()) .test( 'fileSize', 'Size', value => value && value.size <= FILE_SIZE ) .test( 'fileType', 'Format', value => value && SUPPORTED_FORMATS.includes(value.type) ) } }), How can I do it?
create a promise that will load the image file and return the dimension const imageWidthAndHeight = (provideFile) => { // take the given file (which should be an image) and return the width and height const imgDimensions = { width: null, height: null }; return new Promise(resolve => { const reader = new FileReader(); reader.readAsDataURL(provideFile); reader.onload = function () { const img = new Image(); img.src = reader.result; img.onload = function () { imgDimensions.width = img.width; imgDimensions.height = img.height; resolve(imgDimensions); } }; }); } Call and await promise within a custom yup function (using addMethod) and add additional validation to check width and height. const imageDimensionCheck = Yup.addMethod(Yup.mixed, 'imageDimensionCheck', function (message, requiredWidth, requiredHeight) { return this.test("image-width-height-check", message, async function (value) { const { path, createError } = this; if (!value) { return; } const imgDimensions = await imageWidthAndHeight(value); if (imgDimensions.width !== requiredWidth) { return createError({ path, message: `The file width needs to be the ${requiredWidth}px!` }); } if (imgDimensions.height !== requiredHeight) { return createError({ path, message: `The file height needs to be the ${requiredHeight}px!` }); } return true; }); }); Call the created Yup method within formik <Formik initialValues={{ bookCoverPhoto: null, }} validationSchema={ Yup.object().shape({ bookCoverPhoto: Yup.mixed() .required('You need to provide a file') .imageDimensionCheck('test', 1988, 3056) }) } > ....Stuff </Formik>
I managed to do it by using this function function checkAspectRatio(value) { return new Promise(resolve => { const reader = new FileReader(); reader.readAsDataURL(value); reader.onload = function(value) { const img = new Image(); img.src = value.target.result; img.onload = function() { const aspectRatio = this.width / this.height; resolve(aspectRatio); }; }; }); } using Object.defineProperty() to define a new property on the object Object.defineProperty(file, 'aspectRatio', { value: await checkAspectRatio(file) }); and test the value using yup .test( 'fileAspectRatio', 'Please recheck the image resolution', value => value && value.aspectRatio === 1.6 );
image: Yup.mixed() .required("Image is required.") .test( "aspectRatio", "Aspect ratio must be 16:9", value => { return new Promise(resolve => { const reader = new FileReader(); reader.readAsDataURL(value[0]); reader.onload = function(value) { const img = new Image(); img.src = value.target.result; img.onload = function() { const aspectRatio = this.width / this.height; resolve(aspectRatio === (16 / 9)); }; }; }); } ),
Here are two alternative method that are faster than (de)encoding to/from base64 and dose not need the filereader function checkAspectRatio (file) { const img = new Image() img.src = URL.createObjectURL(file) return img.decode().then(() => { URL.revokeObjectURL(img.src) return img.width / img.height }) } // not as cross compatible function checkAspectRatio (file) { return createImageBitmap(file) .then(bitmap => bitmap.width / bitmap.height) }
What is the difference between XMLHttpRequest and Service Worker API to preload assets of my web application
I have the code below to preload specific files into the cache and it works fine using XMLHttpRequest. The question is what is the difference between my approach and using the Service Worker API if I only want to use it on Chrome. What are the pros and cons of Service Worker API compare to using XMLHttpRequest? If they both save data to browser's cache why I should use Service Worker? preload(); function preload(){ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.Preload = factory()); }(this, (function () { 'use strict'; function preloadOne(url, done) { const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'blob'; xhr.onprogress = event => { if (!event.lengthComputable) return false let item = this.getItemByUrl(event.target.responseURL); item.completion = parseInt((event.loaded / event.total) * 100); item.downloaded = event.loaded; item.total = event.total; this.updateProgressBar(item); }; xhr.onload = event => { let type = event.target.response.type; let blob = new Blob([event.target.response], { type: type }); let url = URL.createObjectURL(blob); let responseURL = event.target.responseURL; let item = this.getItemByUrl(responseURL); item.blobUrl = url; item.fileName = responseURL.substring(responseURL.lastIndexOf('/') + 1); item.type = type; item.size = blob.size; done(item); }; xhr.onerror = event => { console.log('Error has happend so we restart the preloading..'); preload(); }; xhr.send(); } function updateProgressBar(item) { var sumCompletion = 0; var maxCompletion = this.status.length * 100; for (var itemStatus of this.status) { if (itemStatus.completion) { sumCompletion += itemStatus.completion; } } var totalCompletion = parseInt((sumCompletion / maxCompletion) * 100); if (!isNaN(totalCompletion)) { this.onprogress({ progress: totalCompletion, item: item }); } } function getItemByUrl(rawUrl) { for (var item of this.status) { if (item.url == rawUrl) return item } } function fetch(list) { return new Promise((resolve, reject) => { this.loaded = list.length; for (let item of list) { this.status.push({ url: item }); this.preloadOne(item, item => { this.onfetched(item); this.loaded--; if (this.loaded == 0) { this.oncomplete(this.status); resolve(this.status); } }); } }) } function Preload() { return { status: [], loaded: false, onprogress: () => {}, oncomplete: () => {}, onfetched: () => {}, fetch, updateProgressBar, preloadOne, getItemByUrl } } return Preload; }))); const preload = Preload(); preload.fetch([ 'https://round-arm-authority.000webhostapp.com/Ultimate%20Video%20Hack/videos/vid1.mp4' ]).then(items => { // use either a promise or 'oncomplete' console.log(items); }); preload.oncomplete = items => { console.log(items); } preload.onprogress = event => { console.log(event.progress + '%'); } preload.onfetched = item => { console.log(item); } };
A service worker allows you to intercept navigation requests and serve HTML directly from the cache, while still having an out-of-band mechanism for updating that HTML as you deploy changes to your site. A service worker allows you to use more complex update and fallback strategies for your subresources than the HTTP cache allows.
var result = await someFunc() returns an object but I expected list of object
I wrote following function for loading indexeddb. (from IndexedDB 備忘メモ) I think this function should return Array of object. But, sometimes it returns an object. What are the possibilities of bug ? Chrome developer tool said type of object was Array during in "load" function. But, after received "records" is type of object. async function load(dbobj, db, index, range) { return new Promise(async (resolve, reject) => { const saves = []; const req = db.transaction(dbobj.storeName).objectStore(dbobj.storeName).index(index).openCursor(range); const onfinished = () => { console.log(`${saves.length} saves found.`); if (saves.length > 0) { resolve(saves[saves.length - 1]); } }; req.onerror = reject; req.onsuccess = (ev) => { const cur = ev.target.result; if (cur) { saves.push(cur.value); cur.continue(); } else { onfinished(); } }; }); } // example of receiving data var records = await load(dbobj, db, index, range);
you are resolving only the value at the last index! resolve(saves) if you need the entire array; async function load(dbobj, db, index, range) { return new Promise(async (resolve, reject) => { const saves = []; const req = db.transaction(dbobj.storeName).objectStore(dbobj.storeName).index(index).openCursor(range); const onfinished = () => { console.log(`${saves.length} saves found.`); if (saves.length > 0) { resolve(saves); // you are resolving only the value at the last index! resolve(saves) if you need the entire array; } }; req.onerror = reject; req.onsuccess = (ev) => { const cur = ev.target.result; if (cur) { saves.push(cur.value); cur.continue(); } else { onfinished(); } }; }); }
How to use callback inside loop?
The background is that I allow user drags multiple files into Dropzone. I need to check each file type. If one of them is no allowed, set message and get out early. Please see code below Starts at for (let i = 0; i < acceptedFiles.length; i++) { In side this for loop, I call reader.onloadend, which is a callback. How do I run callback inside for-loop? // Keep it internal const getMimetype = signature => { switch (signature) { case '89504E47': return 'image/png'; case '47494638': return 'image/gif'; case '25504446': return 'application/pdf'; case 'FFD8FFDB': case 'FFD8FFE0': return 'image/jpeg'; case '504B0304': return 'application/zip'; default: return 'Unknown filetype'; } }; const onDropAccepted = useCallback(acceptedFiles => { // reset to default state resetToDefaultState(); //test console.log('acceptedFiles', acceptedFiles); // reader const reader = new FileReader(); let file; // Multi if (config.isMultipleFiles === true) { // Loop all files and check file types for (let i = 0; i < acceptedFiles.length; i++) { file = acceptedFiles[i]; // get 1st 4 byptes const blob = file.slice(0, 4); reader.readAsArrayBuffer(blob); reader.onloadend = evt => { if (evt.target.readyState === FileReader.DONE) { const uint = new Uint8Array(evt.target.result); let bytes = []; uint.forEach(byte => { bytes.push(byte.toString(16)); }); const hex = bytes.join('').toUpperCase(); const type = getMimetype(hex); // type is allowed if (config.fileTypes.includes(type)) { setFiles([...files, ...acceptedFiles]); } else { // type no good setIsInvaildFileType(true); } } }; } } else { // drop 1 file if (acceptedFiles.length <= 1) { // bucket no file if (files.length === 0) { file = acceptedFiles[0]; // 1st 4 bytes const blob = file.slice(0, 4); // read 4 bytes reader.readAsArrayBuffer(blob); // later reader.onloadend = evt => { if (evt.target.readyState === FileReader.DONE) { // event res to unit const uint = new Uint8Array(evt.target.result); // byte let bytes = []; // loop each unit uint.forEach(byte => { bytes.push(byte.toString(16)); }); // hex const hex = bytes.join('').toUpperCase(); const type = getMimetype(hex); //test console.log('hex', hex); console.log('output', type); // type is allowed if (config.fileTypes.includes(type)) { setFiles([...files, ...acceptedFiles]); } else { // type no good setIsInvaildFileType(true); } } }; } else { // bucket has file already setIsMaxFileNum(true); } } else { // drop multiple files, no thinking of bucket setIsMaxFileNum(true); } } });
I've also had to validate per file using react-dropzone in a similar way. My workaround was to promisify FileReader. 1️⃣ This is the promisified version of "FileReader" const isValidFile = file => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = evt => { // other logic removed for brevity... 2️⃣ Your custom logic dictates, if the file is valid or not if (config.fileTypes.includes(type)) { resolve(true); } else { resolve(false); } }; 3️⃣ Should there was an error, this file is not good. reader.onerror = () => resolve(false) 4️⃣ Start the reading process. const blob = file.slice(0, 4); reader.readAsArrayBuffer(blob); }); }; Now you can use that within the for loop you mentioned. const onDropAccepted = useCallback(acceptedFiles => { // reset to default state resetToDefaultState(); 1️⃣ As `useCallback` accepts a non-async method, Create a wrapped async method we can call here. const processFiles = async () => { if (config.isMultipleFiles === true) { for (let i = 0; i < acceptedFiles.length; i++) { const file = acceptedFiles[i]; 2️⃣ Here is where we validate the file using the code above. const isValid = await isValidFile(file); if (!isValid) { setIsInvaildFileType(true); return; } } 3️⃣ At this point, all files are good. setFiles([...files, ...acceptedFiles]); } else { // removed for brevity... } }; 4️⃣ Let's fire up the process processFiles(); });
IndexDB cursor .onsucess as a promise
First of all, I can't find a suitable title for this question - please feel free to edit. I have the following function that reads objects from indexDb, loadNeededParcels = property => { const dbResults = []; var readTransaction = this.db .transaction("parcelData") .objectStore("parcelData"); readTransaction.openCursor().onerror = e => { console.log("open cursor error ", e); }; readTransaction.openCursor().onsuccess = e => { const cursor = e.target.result; if (cursor) { dbResults.push(cursor.value); cursor.continue(); } else { return dbResults; } }; }; Now when I call this function with a simple function call, for example: console.log(loadNeededParcels('hasData')) The console log is undefined. I am guessing this happens because the function does not wait for the cursor to finish and return the dbResults variable? So my question is this - how can I re-write this function as a promise, or rather to wait for the readTransaction.openCursor().onsucess to trigger? So the expected result is for the function to actually return the values read from the database before exiting. I am using cursors since the .getAll() the method is not supported in IE.
A simple solution that I ended up using: loadNeededParcels = property => { return new Promise((resolve, reject) => { var readTransaction = this.db .transaction("parcelData") .objectStore("parcelData"); readTransaction.openCursor().onerror = e => { reject(e); }; const dbResults = []; readTransaction.openCursor().onsuccess = e => { const cursor = e.target.result; if (cursor) { dbResults.push(cursor.value); cursor.continue(); } else { resolve(dbResults); } }; }); };
Try something like this. Do not call openCursor twice, that creates two requests. function loadNeededParcels(db, property) { return new Promise(function(resolve, reject) { var results = []; var tx = db.transaction('parcelData'); tx.onerror = function(event) { reject(tx.error); }; var store = tx.objectStore('parcelData'); // Open a cursor over all items var request = store.openCursor(); request.onsuccess = function(event) { var cursor = request.result; if(cursor) { var value = cursor.value; if(value) { // Only append defined values to the array results.push(cursor.value); } cursor.continue(); } else { resolve(results); } }; }); } loadNeededParcels(db, 'hasData').then(function(results) { console.log('Results', results); }).catch(function(error) { console.error(error); });