javascriptnode.jspromiseqwhen-js

Promises: Refactoring Progress when the number of progress updates is unknown


The progress handler has been deprecated in a few of the leading promise libraries (Q, When, Bluebird), and has also been dropped from the new Promises/A+ spec. Though I understand the reason behind doing away with progress events, I'm having trouble re-factoring the following pattern that I've become quite used to:

var download = function(url) {
  var deferred = Q.defer();
  ...
  http.get(url, function(res) {
    res.on('data', function(chunk) {
      data += chunk.toString();
      deferred.notify('Downloading: ' + (data.length / totalLength) + '%'); 
      /* ^Notify download progress, but progress events are now deprecated :( */
    });
    res.on('end', function() {
      deferred.resolve('Success: ' + url);
    });
    res.on('error', function(err) {
      deferred.reject(err);
    });
  });
  return deferred.promise;
}
...
download(url).then(console.log, console.log, console.log); 
/* ^The third handler keeps logging progress, but this is now deprecated :( */

I've seen code re-factor examples popping up everywhere on the web, but in all these examples, the number of progress updates seems to be known beforehand. In the pattern above, the number of progress updates that may be issued is indefinite.

Can anybody out there help me implement the above pattern without using progress events/handlers?


Solution

  • The problem with progress is that it was orthogonal to what a promise is and did not compose well. The way you'd do it is from the function.

    The way I'd do it (you can also read it at the Bluebird API docs) is something along these lines:

    var download = function(url, newData) {
      var deferred = Q.defer();
      ...
      http.get(url, function(res) {
        res.on('data', function(chunk) {
          data += chunk.toString();
          if(typeof newData === "function") newData(data);
        });
        res.on('end', function() {
          deferred.resolve('Success: ' + url);
        });
        res.on('error', function(err) {
          deferred.reject(err);
        });
      });
      return deferred.promise;
    }
    ...
    download(url, logOnNewData).then(log, log); 
    

    Also note you can/should use the promise constructor in favor of deferreds as it is throw safe.