Execute .Then() promise NodeJS - arrays

I am trying to figure out how to call a Promise using ".then" so I can continue performing other functions on the return Promise output. I have confirmed the function being used works as expected, but after watching videos and other SO examples am still having a hard time getting this working. See below snippet:
const fs = require('fs');
const JSZip = require('jszip');
const directoryFile = fs.readdirSync('./zipped');
//console.log(directoryFile);
var zip = new JSZip();
var dir = ('./zipped/');
const readZip = async () => {
const promiseToken = new Promise((resolve, reject) => {
fs.readFile((dir + directoryFile), function (err, data) {
if (err) throw err;
JSZip.loadAsync(data).then(function (zip) {
const files = Object.keys(zip.files);
console.log(files);
files.forEach((files) => {
const pkgName = files.substring(files.indexOf("_", files.indexOf("_")) + 1, files.lastIndexOf("_"));
const fileExt = files.split('.').pop();
const pkgExtract = (pkgName + "." + fileExt);
});
})
});
return promiseToken;
});
};
console.log('Program Starts');
readZip().then((promiseToken) => {
console.log(promiseToken.join(','));
});
console.log('Program Ends');
I keep getting "(node:1144376) UnhandledPromiseRejectionWarning: ReferenceError: Cannot access 'promiseToken' before initialization" The above code block take an array of file names and loops through each element and extracts portions of the file name and joins the portions of each elements name that I need to create a new file name. Again, the function works and is tested. What is not working is when I try to call the return output of "readZip()" via ".then()". I need to do get this portion working so I can continue on with the rest of the script. I am new to NodeJS and Javascript and any assistance would be appreciated as none of the videos or examples I have attempted seem to be working.....

You seem to have missed a line, and are returning promiseToken from the promise's constructor instead of from the definition of readZip. Therefore, readZip returns nothing, and you get an undefined value.
Move it to the correct place and you should be fine:
const readZip = async () => {
const promiseToken = new Promise((resolve, reject) => {
fs.readFile((dir + directoryFile), function (err, data) {
if (err) throw err;
JSZip.loadAsync(data).then(function (zip) {
const files = Object.keys(zip.files);
console.log(files);
files.forEach((files) => {
const pkgName = files.substring(files.indexOf("_", files.indexOf("_")) + 1, files.lastIndexOf("_"));
const fileExt = files.split('.').pop();
const pkgExtract = (pkgName + "." + fileExt);
});
})
});
});
return promiseToken; // Moved here
};

You need to:
return the promise in your readZip function (currently you are not doing that)
resolve the promise with your desired data (currently you are not resolving at all)
Use that data in the .then like so:
readZip().then((data) => {
// Use the data to do whatever you want
});

This can be a lot simpler if you use the fs.promises interface where fs.promises.readfile() is already a version that returns a promise:
const readZip = async () => {
const data = await fs.promises.readFile(dir + directoryFile);
const zip = await JSZip.loadAsync(data);
const files = Object.keys(zip.files);
console.log(files);
return files;
};
readZip.then(files => {
for (file of files) {
const pkgName = file.substring(file.indexOf("_", file.indexOf("_")) + 1, file.lastIndexOf("_"));
const fileExt = file.split('.').pop();
const pkgExtract = pkgName + "." + fileExt;
console.log(pkgExtract);
}
})

Related

Is TypeScript linter being overly cautious?

I'm getting a warning from my linter about something in this function:
React.useEffect(() => {
const uploadFiles = async (fileList: FileList) => {
const uploadSessionId = getUUID();
let mediaSetId: string | null = null;
await getMediaSetId().then(response => {mediaSetId = response});
for (let i = 0; i < fileList.length; i++) {
const file: File = fileList[i];
const originalFilename = file.name;
const fileExtension = originalFilename.split('.').pop() ?? 'unknown';
await getBase64(file).then(result => uploadFile(result, originalFilename, fileExtension, file.lastModified, mediaSetId, uploadSessionId));
}
};
if (isUploading && fileList) {
// Start the upload process
uploadFiles(fileList);
}
}, [isUploading, fileList]);
The exact error message is: Function declared in a loop contains unsafe references to variable(s) 'mediaSetId'
When building it, I first thought about putting the for loop within the .then clause but chose to do it as above, whereby I instantiate mediaSetId = null and then the async function call populates it accordingly.
Is this approach I've used "wrong"? It all seems to be working as expected by the way.

Why doesn't the parser wait for Promise.resolve?

I am using React and I do not understand why in the useEffect when running a map function the second part of the code runs before the first part (which is a promise resolve).
Shouldn't the parser wait for the promise to resolve and then run the second part of the code?
useEffect(() => {
const pools = mainnet.Exchanges.Pancakeswap.LpTokens.map((lpPool) => {
// part 1
const [tokenZeroSymbol, tokenOneSymbol] = lpPool.name.replace(' LP', '').split('-');
const prices = fetchTokenPrice(tokenZeroSymbol.toLowerCase(), tokenOneSymbol.toLowerCase());
Promise.resolve(prices).then((values) => {
const [priceTokenZero, priceTokenOne] = values;
filteredFarmPools.find((pool) => {
if (lpPool.name.replace(' LP', '') === pool.name) {
pool.priceTokenZero = values[0].usd;
pool.priceTokenOne = values[1].usd;
}
console.log('inside the fethcprice promise');
});
});
// part 2
filteredFarmPools.find((pool) => {
if (lpPool.name.replace(' LP', '') === pool.name) {
const tvl0 = (pool.reserveTokenZero / 10 ** 18) * pool.priceTokenZero;
const tvl1 = (pool.reserveTokenOne / 10 ** 18) * pool.priceTokenOne;
pool.tvl = tvl0 + tvl1;
}
console.log('inside the tvl calc');
});
});
No.
Promises give you an object that you can pass around and call then on.
They do not turn asynchronous code into blocking code.
The second part of the code isn't inside the then callback so it runs while the asynchronous code (that will trigger the first promise to resolve) is running in the background.
That said, see the await keyword for asyntax that can give the illusion that a promise is blocking.
useEffect(() => {
const processPools = async () => {
for (let lpPool of mainnet.Exchanges.Pancakeswap.LpTokens) {
const [tokenZeroSymbol, tokenOneSymbol] = lpPool.name.replace(' LP', '').split('-');
const values = await fetchTokenPrice(tokenZeroSymbol.toLowerCase(), tokenOneSymbol.toLowerCase());
// Promise.resolve(prices).then((values) => {
const [priceTokenZero, priceTokenOne] = values;
filteredFarmPools.find((pool) => {
if (lpPool.name.replace(' LP', '') === pool.name) {
pool.priceTokenZero = values[0].usd;
pool.priceTokenOne = values[1].usd;
}
console.log('inside the fethcprice promise');
// });
});
}
}
processPools();
});
Original Array.map does not support async
Promise.resolve return immediately, no difference with Promise.then

Trying to update certain fields in useState array in React Native

I'm trying to bring rows of data back from an SQLite database and then iterate through them so I can manipulate the data of each row to present them differently (ie, convert a date into a customised format).
Bringing the data back is fine (from the ReadEntries function), which I do in a useEffect so that it only runs once on the screen load, but the duplicating of the array and then updating the rows doesn't seem to work. I think it might have something to do with the fact setting the value of a useState array isn't quick enough to update for my duplicate array to then take a full snapshot of it.
It sometimes works when I save multiple times in VS Code, presumably because the state is already stored for subsequent screen refreshes.
useEffect(() => {
var results: SQLite.ResultSetRowList;
const response = async (): Promise<any> => {
await ReadEntries(projectID).then((value) => {
results = value as SQLite.ResultSetRowList;
setEntries(results.raw); //this works
let newEntries = [...entries];
for (let i = 0; i < newEntries.length; i++) {
let newStartDate = new Date(newEntries[i].startDate);
newEntries[i].dateOfEntry = newEntries[i].dateOfEntry;
newEntries[i].startDate =
newStartDate.getDate().toString() +
"/" +
newStartDate.getMonth().toString() +
"/" +
newStartDate.getFullYear().toString();
}
setEntries(newEntries);
});
};
response().catch((error) => {
"ERROR: " + error;
});
}, []);
Thanks
State updates with hooks are not only async but are also bound by closure. You should not reply on the updated state to trigger another change in the same function call
Check this post for more details on this: useState set method not reflecting change immediately
You can make use of the the fetched data to perform the operation
useEffect(() => {
var results: SQLite.ResultSetRowList;
const response = async (): Promise<any> => {
await ReadEntries(projectID).then((value) => {
results = value as SQLite.ResultSetRowList;
setEntries(results.raw); // This shouldn't be required.
let newEntries = [...results.raw]; // use results instead of entries
for (let i = 0; i < newEntries.length; i++) {
let newStartDate = new Date(newEntries[i].startDate);
newEntries[i].dateOfEntry = newEntries[i].dateOfEntry;
newEntries[i].startDate =
newStartDate.getDate().toString() +
"/" +
newStartDate.getMonth().toString() +
"/" +
newStartDate.getFullYear().toString();
}
setEntries(newEntries);
});
};
response().catch((error) => {
"ERROR: " + error;
});
}, []);
I achieved this by calling the .raw() function of the returned results, and looping through that array with forEach. I then created a new object (newarrayItem) and assigned the fields to the .raw() array values, then pushed that object into a blank array (newarray) that I assigned the type TentryItemString. Then passed this to the setEntries useState array.
useEffect(() => {
var results: SQLite.ResultSetRowList;
let newarray: TentryItemString[] = [];
const response = async (): Promise<any> => {
await ReadEntries(projectID).then((value) => {
results = value as SQLite.ResultSetRowList;
results.raw().forEach((element, index) => {
let newdate = "";
let newStartDate = new Date(element.startDate);
newdate =
newStartDate.getDate().toString() +
"/" +
(newStartDate.getMonth() + 1).toString() +
"/" +
newStartDate.getFullYear().toString();
var newarrayItem = {
dateOfEntry: "",
startDate: "",
};
newarrayItem.startDate = newdate;
newarrayItem.dateOfEntry = element.dateOfEntry;
newarray.push(newarrayItem);
});
});
newarray.reverse();
setEntries(newarray);
};
response().catch((error) => {
"ERROR: " + error;
});
}, []);
A couple of issues I found in doing this. One, if I initiated the newarrayItem by equalling it to an already created object (ie. var newarray = newarrayItem), the array.push() method seemed to overwrite all rows with the same information. That's why I've instantiated it there and then. The other issue was struggling with assigning the type to the useState as an array, but I finally achieved this with the following syntax:
const [entries, setEntries] = useState();
And all TentryItemString is, is:
type TentryItemString = {
dateOfEntry: string;
startDate: string;
};
Hope someone finds that helpful.

React Ant Design multiple files upload doesn't work

I'm in the process of sending multiple files from "React.js" by formData.append() to a backend.
At the backend(Spring boot), I was able to see that multiple files were saved well with postman.
The problem occurred in React.
(I'm using "Ant Design" that is React UI Library.)
Below is the source that append files to formdata with extra data.
const formData = new FormData();
formData.append('webtoonId', this.state.selectedToonId);
formData.append('epiTitle', this.state.epiTitle);
formData.append('eFile', this.state.thumbnail[0].originFileObj);
for( let i = 0; i< this.state.mains.length ; i++){
formData.append('mFiles', this.state.mains[i].originFileObj);
}
uploadEpi(formData)
uploadEpi() is POST API.
Below is about state.
this.state = {
toons: [],
epiTitle :'',
thumbnail : [],
mains : [],
selectedToonID : ''
}
When I submit, Text and single file are stored in the DB normally, but only multiple files cannot be saved.
There was no error. Just multiple files didn't be saved.
The state "mains" is configured as shown below.
I guess it's because I'm using Ant Design incorrectly.
(Ant Design : https://ant.design/components/upload/)
Why I guessed so, because when I add multiple attribute to <Dragger> like below,
<Dragger onChange={this.onChangeMain} beforeUpload={() => false} multiple={true}>
the state "mains" multiple files became undefined.
Below is onChange={this.onChangeMain}
onChangeMain=({ fileList })=> {
this.setState({ mains : fileList }, function(){
console.log(this.state)
});
}
The bottom line is, I want to know how to upload multiple files through <Upload> (or <Dragger>) in "React Ant Design."
I don't know what should I do.
this is my github about this project.
I'd appreciate with your help. thx.
const [loading, setLoading] = useState<boolean>(false);
const [fileList, setFileList] = useState<any[]>([]);
const [/* fileListBase64 */, setFileListBase64] = useState<any[]>([]);
const propsUpload = {
onRemove: (file:any) => {
const index = fileList.indexOf(file);
const newFileList:any = fileList.slice();
newFileList.splice(index, 1);
return setFileList(newFileList)
},
beforeUpload: (file:any) => {
setFileList([...fileList, file]);
return false;
},
onChange(info:any) {
setLoading(true);
const listFiles = info.fileList;
setFileList(listFiles);
const newArrayFiles = listFiles.map((file:any) => file.originFileObj? (file.originFileObj) : file );
const anAsyncFunction = async (item:any) => {
return convertBase64(item)
}
const getData = async () => {
return Promise.all(newArrayFiles.map((item:any) => anAsyncFunction(item)))
}
getData().then(data => {
/* setFileSend(data) */
setFileListBase64(data);
setLoading(false);
// console.log(data);
});
},
directory: true,
fileList: fileList,
};
const convertBase64 = (file:File) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsDataURL(file)
fileReader.onload = () => {
resolve(fileReader?.result);
}
fileReader.onerror = (error) => {
reject(error);
}
})
}
const handleDeleteListFiles = () => {
setFileList([]);
setFileListBase64([]);
}
It seems like you are overriding the value of mFiles.
const formData = new FormData();
formData.append('webtoonId', this.state.selectedToonId);
formData.append('epiTitle', this.state.epiTitle);
formData.append('eFile', this.state.thumbnail[0].originFileObj);
let mFiles = [];
for( let i = 0; i< this.state.mains.length ; i++){
mFiles[i] = this.state.mains[i].originFileObj;
}
formData.append('mFiles', mFiles)
uploadEpi(formData)
Maybe this can work: formData.append('mFiles[]', mFiles)
If you add [] to the string it should not overwrite but add to the array

Firebase upload multiple files and get status

I have a React form where the user can upload multiple files. These are stored in fileList
async function uploadFiles(id) {
try {
const meta = await storageUploadFile(fileList, id);
console.log(meta);
} catch (e) {
console.log(e);
}
}
This calls my helper function that uploads the files to Firebase
export const storageUploadFile = function(files, id) {
const user = firebase.auth().currentUser.uid;
return Promise.all(
files.map((file) => {
return storage.child(`designs/${user}/${id}/${file.name}`).put(file)
})
)
};
What I'd like is on calling uploadFiles, get the total filesize of all items, and then show the overall progress.
At the moment, my code is only returning the file status in an array on completion
[
{bytesTransferred: 485561, totalBytes: 485561, state: "success"},
{bytesTransferred: 656289, totalBytes: 656289, state: "success"}
]
This is the way i do it:
import Deferred from 'es6-deferred';
export const storageUploadFile = function(files, id) {
const user = firebase.auth().currentUser.uid;
// To track the remaining files
let itemsCount = files.length;
// To store our files refs
const thumbRef = [];
// Our main tasks
const tumbUploadTask = [];
// This will store our primses
const thumbCompleter = [];
for (let i = 0; i < files.length; i += 1) {
thumbRef[i] = storage.ref(`designs/${user}/${id}/${file.name}`);
tumbUploadTask[i] = thumbRef[i].put(files[i]);
thumbCompleter[i] = new Deferred();
tumbUploadTask[i].on('state_changed',
(snap) => {
// Here you can check the progress
console.log(i, (snap.bytesTransferred / snap.totalBytes) * 100);
},
(error) => {
thumbCompleter[i].reject(error);
}, () => {
const url = tumbUploadTask[i].snapshot.metadata.downloadURLs[0];
itemsCount -= 1;
console.log(`Items left: ${itemsCount}`)
thumbCompleter[i].resolve(url);
});
}
return Promise.all(thumbCompleter).then((urls) => {
// Here we can see our files urls
console.log(urls);
});
};
Hope it helps.

Resources