Search code examples
vue.jses6-promise

Completing one task before undertaking another using promises


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


Solution

  • 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();
      }
    }