0

I'm uploading a CSV file to the server, then in the backend I split that file into multiple smaller files (because these files can get quite big). After uploading and splitting the file, I then want to process the file to insert some new data to the database.

All the logic works properly, however, I need to run some cleanup code after all files have finished processing. After researching for a while I found that I can make this happen by using Javascript promises.

I have always had problems understanding Javascript promises, it's the second night in a row that I stay up until past midnight to try and get this finished, so I don't think my understanding of Javascript promises will improve that much this soon, I just need to get this done.

Anyone with experience in Javascript promises, can you see what I'm doing wrong or what I'm missing? My code below is quite explanatory (I think).

function sendAjaxRequest(data, callback, addUploadEvents = false) {
  return new Promise(function (resolve, reject) {
    let xhr = new XMLHttpRequest();
    xhr.open('POST', ajaxurl);

    // when finished, we run our passed callback
    xhr.onload = callback;

    // only when uploading the file
    if ( addUploadEvents ) {
      xhr.addEventListener('loadstart', function() {
        console.log('uploading file');
      });

      xhr.addEventListener('progress', function(evt) {
        console.log('loaded: ' + evt.loaded + ', total: ' + evt.total);
      });

      xhr.addEventListener('loadend', function() {
        console.log('file uploaded');
      });
    }

    // general events to catch errors
    xhr.addEventListener('timeout', function(e) {
      console.log('the request has timed out', e);
      reject(e);
    });

    xhr.addEventListener('error', function(e) {
      console.log('the request returned an error', e);
      reject(e);
    });

    xhr.addEventListener('abort', function(e) {
      console.log('the request was aborted', e);
      reject(e);
    });

    // send the request
    xhr.send(data);
  });
}

function uploadFile() {
  let data = new FormData(),
    f = document.querySelector('form [name="csv"]').files;

  data.append('csv', f[0]);

  // send the request
  let req = sendAjaxRequest(data, function() {
    if ( this.status === 200 ) {
      const response = JSON.parse(this.response);

      if ( ! response.success ) {
        console.log('error in the uploadFile request');
        return false;
      }

      // once the file is uploaded, we want to split it into several smaller pieces
      splitToMultipleFiles();
    }
    else {
      // should not reach this ever, I think I covered all problematic cases
      console.log('there was an error in the request', this);
    }
  }, true);

  // returning false to prevent the form from reloading the page
  return false;
}

function splitToMultipleFiles() {
  let data = new FormData();

  // send the request
  let req = sendAjaxRequest(data, function() {
    if ( this.status === 200 ) {
      const response = JSON.parse(this.response);

      if ( ! response.success ) {
        console.log('error in the splitToMultipleFiles request');
        return;
      }

      // response.data.files has an array of files I want to process in the backend one at a time
      let processingFiles = response.data.files.map(function(file) {
        return processFile(file);
      });

      // wait until all files have been processed, then run some cleanup logic
      // the problem is, the promises are never resolved :(
      Promise.all(processingFiles).then(function() {
        console.log('all done, cleanup your mess');
      });
    }
  });
}

function processFile(tempFile) {
  let data = new FormData();

  data.append('temp-file', tempFile);

  // send the request
  return sendAjaxRequest(data, function() {
    if ( this.status === 200 ) {
      const response = JSON.parse(this.response);

      if ( ! response.success ) {
        console.log('error in the processFile request for file ' + tempFile);
        return;
      }

      console.log('file finished processing: ' + tempFile);
    }
  });
}
andrux
  • 2,782
  • 3
  • 22
  • 31
  • If you want to use promises, you should not pass a callback to receive the asynchronous result. See [How do I promisify native XHR?](https://stackoverflow.com/q/30008114/1048572) for how to do it. Then use `.then(response => { … })` in `processFile`. – Bergi Nov 22 '20 at 12:20

1 Answers1

0

Well, I guess I will need to schedule a good course on Javascript promises as a next year objective.. for now, I was able to solve my dilemma like this, probably not the best way but it did work:

function sendAjaxRequest(data, callback, addUploadEvents = false) {
  let xhr = new XMLHttpRequest();
  xhr.open('POST', ajaxurl);

  // when finished, we run our passed callback
  xhr.onload = callback;

  // only when uploading the file
  if ( addUploadEvents ) {
    xhr.addEventListener('loadstart', function() {
      console.log('uploading file');
    });

    xhr.addEventListener('progress', function(evt) {
      console.log('loaded: ' + evt.loaded + ', total: ' + evt.total);
    });

    xhr.addEventListener('loadend', function() {
      console.log('file uploaded');
    });
  }

  // general events to catch errors
  xhr.addEventListener('timeout', function(e) {
    console.log('the request has timed out', e);
  });

  xhr.addEventListener('error', function(e) {
    console.log('the request returned an error', e);
  });

  xhr.addEventListener('abort', function(e) {
    console.log('the request was aborted', e);
  });

  // send the request
  xhr.send(data);
}

function uploadFile() {
  let data = new FormData(),
    f = document.querySelector('form [name="csv"]').files;

  data.append('csv', f[0]);

  // send the request
  let promise = new Promise(function(resolve, reject) {
    sendAjaxRequest(data, function() {
      if ( this.status === 200 ) {
        const response = JSON.parse(this.response);

        if ( ! response.success ) {
          console.log('error in the uploadFile request');
          return false;
        }

        resolve(response.data);
      }
      else {
        // should not reach this ever, I think I covered all problematic cases
        console.log('there was an error in the request', this);
        reject(this.status);
      }
    }, true);
  });

  promise.then(splitToMultipleFiles, function(err) {
    console.log('promise rejected', err);
  });

  // returning false to prevent the form from reloading the page
  return false;
}

function splitToMultipleFiles() {
  let data = new FormData();

  // send the request
  let promise = new Promise(function(resolve, reject) {
    sendAjaxRequest(data, function() {
      if ( this.status === 200 ) {
        const response = JSON.parse(this.response);

        if ( ! response.success ) {
          console.log('error in the splitToMultipleFiles request');
          return;
        }

        // response.data.files has an array of files I want to process in the backend one at a time
        resolve(response.data.files);
      }
      else {
        reject(this.status);
      }
    });
  });

  promise.then(function(files) {
    let processingFiles = files.map(function (file) {
      return processFile(file);
    });

    // wait until all files have been processed, then run some cleanup logic
    Promise.all(processingFiles).then(function () {
      console.log('all done, cleanup your mess');
    });
  }, function(err) {
    console.log('promise rejected', err);
  });
}

function processFile(tempFile) {
  let data = new FormData();

  data.append('temp-file', tempFile);

  // send the request
  return new Promise(function(resolve, reject) {
    sendAjaxRequest(data, function() {
      if ( this.status === 200 ) {
        const response = JSON.parse(this.response);

        if ( ! response.success ) {
          console.log('error in the processFile request for file ' + tempFile);
          return;
        }

        console.log('file finished processing: ' + tempFile);
        resolve(response.data);
      }
      else {
        reject(this.status);
      }
    });
  });
}
andrux
  • 2,782
  • 3
  • 22
  • 31