3

I'm having trouble finding any solutions for tracking the upload progress event of an XMLHttpRequest object inside of a Promise.

Here is an example of the code that I'm using to create async requests:

var request = function(method, url, data) {
  return new Promise(function(resolve, reject) {
    xhr.open(method, url, true);
    xhr.onload = resolve;
    xhr.onerror = reject;
    xhr.send(data);
  });
};

I'd like to have something like this:

  var r = request('POST', '/upload', data)
    .then(() => {
      console.log('completed');
    });

  r.upload.addEventListener('progress', (e) => {
    console.log('ProgressEvent: ', e);
  });

I'm trying to do this with vanilla JavaScript which is working well so far, but I'd like to figure out a clean way to do this without using any third party libraries.

Alexander O'Mara
  • 52,993
  • 16
  • 139
  • 151
ZiggidyCreative
  • 187
  • 1
  • 11
  • JQuery 3.x is Promises/A+ compliant and its $.ajax function supports progress events: https://api.jquery.com/deferred.progress/ – PHP Guru Nov 16 '20 at 16:07

2 Answers2

3

Unfortunately base promises don't have a way of representing any kind of progress. They are only pass/fail and can only send one result. You can think of them more as a delayed function return rather than a callback. You will probably need to accept a progress callback from your function and send that information out an alternate route. If you use a promise library some of them do have a progress callback option (ex. https://github.com/kriskowal/q search for "Progress Notification")

waterfoul
  • 313
  • 3
  • 6
3

With some caveats for older browsers, you can use ES6 classes to extend the Promise constructor and add your own API on top.

Here is a basic example adding an onReadyStateChange event handler to a Promise AJAX wrapper.

'use strict';

const _onReadyStateChange = Symbol('onReadyStateChange');

class PromiseAJAX extends Promise {
    constructor(func) {
        let promise;
        super((resolve, reject) => {
            func(resolve, reject, function(event) {
                // Constructor will not finish before readyState 1 event.
                // That means we will not have access to promise in time.
                // Could use a setTimeot of 0 if desired to capture.
                let onReadyStateChange;
                if (promise && (onReadyStateChange = promise.onReadyStateChange)) {
                    onReadyStateChange(event);
                }
            });
        });
        promise = this;
        this[_onReadyStateChange] = undefined;
    }

    get onReadyStateChange() {
        return this[_onReadyStateChange];
    }

    set onReadyStateChange(value) {
        this[_onReadyStateChange] = value;
    }
}

////////////////////////////////////////////////////////////////////////////////

let pajax = new PromiseAJAX(function(resolve, reject, readyStateChange) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = readyStateChange;
    xhr.onload = resolve;
    xhr.onerror = reject;
    xhr.open('GET', 'https://crossorigin.me/https://www.google.com/humans.txt', true);
    xhr.send();
});

pajax.then(function(value) {
    console.log(value.target.responseText);
});
pajax.catch(function(reason) {
    console.log(reason);
});
pajax.onReadyStateChange = function(event) {
    console.log('readyState: ' + event.target.readyState);
};

It's not entirely perfect, but should give you the basics of extending a Promise if desired.

Alexander O'Mara
  • 52,993
  • 16
  • 139
  • 151
  • Inheritance works in ES5 too. It just doesn't look like this. (This still doesn't handle progress events btw.) – PHP Guru Nov 16 '20 at 16:44