katie hudson
katie hudson

Reputation: 2893

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

Upvotes: 1

Views: 559

Answers (2)

Phil
Phil

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

Aurel B&#237;l&#253;
Aurel B&#237;l&#253;

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

Related Questions