Reputation: 2893
I am working in VueJS. I have a simple form, where I have a file input plus one more field.
<form @submit.prevent="formSubmit()">
<div class="card-body">
<div class="form-group">
<label for="someName">Some Name</label>
<input type="text" class="form-control" id="someName"
placeholder="someName" v-model="form.someName">
</div>
<div class="form-group">
<label for="uploadedFile">Data</label>
<input type='file' ref="file" @change='handleCSVUpload' accept=".csv"
id="uploadedFile">
</div>
</div>
<button class="btn btn-success">Submit</button>
</form>
So what I want to do is as soon as I add a file, I want to assign it to a data variable. As such, I have the @change
which triggers this
export default {
data() {
return {
csvFile: '',
parsedData: '',
form: new Form({
someName: ''
})
}
},
methods: {
handleCSVUpload() {
this.csvFile = this.$refs.file.files[0];
}
}
}
This is all fine. My problem comes next. When the form is submitted, I want to firstly parse this file to JSON. Once this is done, I then want to submit this JSON to the backend along with the other form field. At the moment I have this
import Papa from 'papaparse';
export default {
data() {
return {
csvFile: '',
parsedData: '',
form: new Form({
someName: ''
})
}
},
methods: {
handleCSVUpload() {
this.csvFile = this.$refs.file.files[0];
},
formSubmit() {
this.$Progress.start();
this.processCSVFile(this.csvFile);
this.form.post('api/upload').then(() => {
this.$Progress.finish();
}).catch(() => {
this.$Progress.fail();
})
},
processCSVFile(csv) {
let file = csv;
let config = {
delimiter: "",
newline: "",
quoteChar: '"',
escapeChar: '"',
header: true
};
Papa.parse(file, {
config: config,
error: function (err, file) {
console.log("ERROR:", err, file);
return err;
},
complete: function (results) {
this.parsedData = JSON.stringify(results.data);
console.log(this.parsedData)
}
});
}
}
}
This all works fine, but not really satisfied with it. Within the formSubmit
method I call this.processCSVFile(this.csvFile);
However, I then go straight into posting the data to the backend. Instead, I need to make sure that the parsing is okay, because if not, I need to display an error and not submit anything. Now the following is incorrect, but it shows my thinking
this.processCSVFile(this.csvFile).then(() => {
this.form.post('api/upload').then(() => {
this.$Progress.finish();
}).catch(() => {
this.$Progress.fail();
})
So it should process the CSV file, if this is successful, then submit to the api. However, not sure how to do these multiple promises? Also not too sure what I should be returning or doing within the processCSVFile function?
Any advice appreciated
Thanks
Upvotes: 1
Views: 559
Reputation: 164795
What you want to do here is have processCSVFile
return a Promise
.
You can do this by wrapping the call to Papa.parse
in a new Promise
. For example
processCSVFile (csv) {
let config = { ... } // no change here
return new Promise((resolve, reject) => {
Papa.parse(csv, {
config,
error: (err, file) => {
console.error(err, file)
reject(err)
},
complete: results => {
resolve(JSON.parse(results.data))
}
})
}
Now you can do exactly what you wanted in your submit handler...
this.processCSVFile(this.csvFile).then(parsedData => {
this.parsedData = parsedData // you could also do this in processCSVFile
// note the "return" here so control will bubble up the promise chain
return this.form.post('api/upload')
}).then(() => {
this.$Progress.finish();
}).catch(() => {
// This will catch any failures in "processCSVFile" and / or "this.form.post"
this.$Progress.fail();
})
Upvotes: 0
Reputation: 7973
The first issue here is that your processCSVFile
function does not return a Promise. Unfortunately, the Papa Parse functions "[don't] return anything. Results are provided asynchronously to a callback function." But since they take callbacks you can easily wrap the call in a Promise like so:
processCSVFile(csv) {
let config = {...};
return new Promise((resolve, reject) => {
Papa.parse(csv, {
config: config,
error: reject,
complete: (results) => {
this.parsedData = JSON.stringify(results.data);
resolve(this.parsedData);
}
});
});
}
One of the nice things about the Promise API is the fact that it can be easily chained. In particular, from any Promise handler, you can return a Promise instead of a specific result. So in your code above:
this.processCSVFile(this.csvFile).then(() => {
return this.form.post('api/upload');
}).then(() => {
this.$Progress.finish();
}).catch(() => {
this.$Progress.fail();
});
Your post is also tagged with es6
, so you could instead make use of the great async
/ await
syntax. In this case, you need to change your function to async
, like so:
async formSubmit() {
this.$Progress.start();
try {
await this.processCSVFile(this.csvFile);
await this.form.post('api/upload');
this.$Progress.finish();
} catch (err) {
this.$Progress.fail();
}
}
Upvotes: 2