Reading file in React action - javascript

I am new in javascript and have problem with it's asynchronous behavior. I am trying to read file in React action. Important part looks like this:
if(file){
const reader = new FileReader();
reader.readAsDataURL(inquiryFile);
reader.onload = function() {
body = JSON.stringify({
fileToUpload: reader.result,
});
return dispatch(basicRequest(body));
};
reader.onerror = function(error) {
console.error('Error uploadingFile: ', error);
};
}
else{
return dispatch(basicRequest());
}
The component, which is responsible for calling this action needs to dispatch another action depending on either success or error result.
return submitFileAction(masterId, data).then((result) => {
if (!result.error) {
console.log('success');
} else {
console.log('error');
}
});
Problem is, that result returning to 'then part' is undefined and filereader.onload is called after I get error.
I would like to ask how to await result from filereader.
Thanks.

You probably want to wrap the FileReader into a Promise.
if (file) {
return new Promise(function(resolve, reject){
...
reader.onload = function(){
...
resolve(dispatch(basicRequest(body)));
};
reader.onerror = function(error) { reject(error); };
});
}
The error would be handled as:
return submitFileAction(masterId, data).then((result) => {
console.log('success');
}).catch((error) => {
console.log('error');
});
This presumes dispatch() also returns a Promise.

Related

Can not return from a function

I have a function that looks like following
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
return true;
} else {
console.log("studio not available");
return false;
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
};
however the return statements don't return anything when I use the function in the following manner
useEffect(() => {
const agent = checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("studio available is: ", agent);
}, []);
the console.log massages appear but the return statement is undefined.
any help would be appreciated.
You can not return from a callback function, as it is running asynchronously and you are not waiting for it to have a result ready.
You can however make the function itself async by returning a Promise instead of the actual result and wait until the Promise has a result ready (e.g. it is resolved):
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
reject(); // reject on failure
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
resolve(true); // resolve instead of return
} else {
console.log("studio not available");
resolve(false);
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
).then((agent) => { // then callback is called when the promise resolved
console.log("studio available is: ", agent);
}).catch(error => { // catch is called when promise got rejected
console.log('An error happened');
});
}, []);
The function servceInfo.OnServiceStateChange is a function into the object (seems to be an event).
I'd suggest declaring a variable on the checkForAvailableAgent like connected and change it's value when the event is called.
Then access it using checkForAvailableAgent.connected.
A version with async/await and try/catch
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId,
OnError: reject,
OnServiceStateChange: e => resolve(e.ConnectedAdvisers > 0)
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
(async () => {
try {
const isAvailable = await checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("Result", isAvailable)
} catch(e) {
console.error(e)
}
})()
// console.log("studio available is: ", agent);
}, []);
There are 2 possible reasons
you are not returning anything from checkForAvailableAgent.
After returning from the checkForAvailableAgent, it might be asynchronous function. You can use async & await.

Mapping objects, and using state, and passing value through a function in setState

I have a react application, where i want to set the state equal to an array of objects, that has been passed through a function.
I have two variables, and is the file extension, and one is the raw data base64 encoded.
this.setState({
result: fileItems.map(fileItem => ({
"file": this.findFileTypeHandler(fileItem.file.name),
"data": this.getBase64(fileItem.file)
}))
});
}}>
as of right now i just return the value within the two functions, but the issue occurs, when the functions are not asynchronous.
findFileTypeHandler = file =>{
return file.split('.').pop();
}
getBase64 = file => {
//find en måde at gøre den asynkron
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
console.log(reader.result)
this.setState({base64: reader.result})
return reader.result;
};
reader.onerror = function (error) {
console.log('error is ', error)
};
}
the best case scenario, would be to setState, and then assign the value for each map iteration, but how can i pass the data through the functions, and set the state respectivly?
How about use Promise.all to wait all files to be read, then setState the results.
Promise.all(
fileItems.map(fileItem => new Promise((resolve, reject) => {
//find en måde at gøre den asynkron
var reader = new FileReader();
reader.readAsDataURL(fileItem.file);
reader.onload = () => {
resolve({
file: this.findFileTypeHandler(fileItem.file.name),
data: {
base64: reader.result
}
});
};
reader.onerror = (error) => reject(error);
}))
)
.then((results) => {
this.setState({
result: results
});
})
.catch((error) => console.log(error));

Accessing the Node ReadStream

I have a piece of code which is wrapped in a promise. That piece of code reads an image form http, does various things, and at the end sends it to aws.s3.putObject. It looks like this (simplified):
Please note form is multiparty object.
form.on('part', (part) => {//form is multiparty
fileCount++;
let tmpFile = path.join(os.tmpDir(), `${userId}_${timePrefix}_${path.basename(part.filename)}`);
part.pipe(fs.createWriteStream(tmpFile));
part.on('end', () => {
resolve(fs.createReadStream(tmpFile));
});
part.once('error', (error) => {
handleError(error);
});
});
form.once('error', (error) => {
handleError(error);
});
form.parse(request);
}).then((imageStream) => {
//here is a call to AWS S3.putObject. Which return a promise
}).then(() => {
return new Ok();
});
In essence a stream is made on a created image and sent to AWS. I wanted to do some manipulation on binary level (read file signature, to check if it is an image). I got it to work like this:
form.on('part', (part) => {
fileCount++;
let tmpFile = path.join(os.tmpDir(), `${userId}_${timePrefix}_${path.basename(part.filename)}`);
part.pipe(fs.createWriteStream(tmpFile));
part.on('end', () => {
let chunk = [];
let file = fs.createReadStream(tmpFile);
let isFirstChunkSet = false;
file.on('data', function(chunks) {
if (!isFirstChunkSet){
chunk = chunks;
isFirstChunkSet = true;
}
});
file.on('close', function() {
let magicNumber = chunk.toString('hex', 0, 2);
if (imageProperties.allowed_file_types.includes(magicNumber)) {
resolve(fs.createReadStream(tmpFile));
} else {
error.message = 'wrong file type';
handleError(error);
}
});
});
part.once('error', (error) => {
handleError(error);
});
});
form.once('error', (error) => {
handleError(error);
});
form.parse(request);
}).then((imageStream) => {
//here is a call to AWS S3.putObject. Which return a promise
}).then(() => {
return new Ok();
});
Basically I attached two event listeners to the existing stream to access the data, and so some checking on the header file.
What bothers me is the feeling that I am overdoing things here. I would like to avoid those two listeners (data and close) and read a stream if possible.
To be more precise, this section of code receives the stream, and inside it I would like to access the data before sending it to AWS. This stream is ready, and simply put how do I read it, without using events?
form.parse(request);
}).then((imageStream) => {
//How can I access the imageStream here, without event listeners.
//Assumption is the stream is ready to be accessed.
//here is a call to AWS S3.putObject. Which return a promise
}).then(() => {
return new Ok();
});

For what reasons can reader.readAsArrayBuffer fail?

I am using the Filereader
const arrayBufferPromiseFromBlob = function (blob) {
//argument must be blob or file Object
return new Promise(function (resolve, reject) {
const reader = new FileReader();
reader.onload = function (event) {
resolve(reader.result);
};
reader.onerror = function (error) {
reject(error);
};
reader.readAsArrayBuffer(blob);
});
};
sometimes it rejects (onerror), even though it was given a valid blob, what can be the reason this happens ?
In the calling code using arrayBufferPromiseFromBlob , attach a .catch() to the promise and log the error.

Returning data that came from Promise

I'm creating a script which reads data from pdf in node, I'm using pdf_text_extract, and I'm trying to return the data with Bluebird.
Types.js:
var pdf = require('pdf');
var Types = {
read: function(file, extension) {
pdf.extract(file, function(error, data) {
console.log(data);
});
}
};
module.exports = Types;
The data is a [Function], this is clearly wrong.
Pdf.js:
var Promise = require('bluebird');
var pdf_text_extract = require('pdf-text-extract');
var Pdf = {
extract: function(file, cb) {
return new Promise(function(resolve, reject) {
if (reject) {
console.log(reject);
}
pdf_text_extract(file, function(error, data) {
if (error) {
console.log(error);
}
resolve(data);
});
});
}
};
module.exports = Pdf;
I'm trying to access the data in other archive, which is calling the Types.js.
No, the data is not a function. The cb function you're passing is completely ignored and never executed, the log you are seeing is from console.log(reject); (as reject is always truthy).
Correct would be
var pdf = {
extract: function(file) {
return new Promise(function(resolve, reject) {
pdf_text_extract(file, function(error, data) {
if (error)
reject(error);
else
resolve(data);
});
});
}
};
var types = {
read: function(file, extension) {
return pdf.extract(file).then(function(data) {
console.log(data);
}, function(error) {
console.log(error);
});
// returns a promise that fulfills with undefined once data or error are received
}
};
Or much simpler
var pdf = {
extract: Promise.promisify(pdf_text_extract)
};

Categories