tsw_mik
tsw_mik

Reputation: 81

jQuery deferred promise progress notification

I've been playing with promises and trying to build some sort of progress notification.

The code is executing all functions in the right order, but the progress updates execute just before the resolve as opposed to when they actually happen.

Can anyone point out what I'm doing wrong?

  function start(x) {
    console.log("Start: " + x);
    var promise = process(x);
    console.log("promise returned");
    promise.then(function(data) {
        console.log("Completed: " + data);
    }, function(data) {
        console.log("Cancelled: " + data);
    }, function(data) {
        console.log("In Progress: " + data);
    });
  }

  function process(x) {
    var deferred = $.Deferred();
    var promise = deferred.promise();

    // process asynchronously
    setTimeout(function() {
      for (var i=0 ; i<x ; i++) {
        sleep(1000);
        deferred.notify(i);
      }

      if (x % 2 === 0) {
        deferred.reject(x);
      } else {
        deferred.resolve(x);
      }
    }, 0);

    return promise;
  }

  function sleep(sleepDuration) {
    var now = new Date().getTime();
    while(new Date().getTime() < now + sleepDuration){ /* do nothing */ }
  }

  start(3);

Fiddle here: https://jsfiddle.net/n86mr9tL/

Upvotes: 1

Views: 601

Answers (1)

Roamer-1888
Roamer-1888

Reputation: 19288

A delay timer implemented with while(), will "block" - ie hog the processor.

Blocking not only prevents other javascript from running but also inhibits reliable refreshing of the browser screen including the console. So whereas those deferred.notify(i) and console.log("In Progress: " + data) statements are firing, the console isn't refreshed until the processor becomes free to do so.

Unsurprisingly, the solution lies in not using while().

Fortunately, javascript imcludes two built-in methods window.setTimeout() and window.setInterval(), which are conceptually different from a while() idler but fulfil the same role .... without blocking.

  • window.setInterval(fn, t) fires function fn every t milliseconds,
  • window.setTimeout(fn, t) fires function fn once, after t milliseconds.

Both methods return an opaque reference, which allows them to be cancelled.

In the code below, start() is unmodified, process() is heavily modified and sleep() has disappeared.

process() now does the following :

  • creates a jQuery Deferred and returns a promise derived from it,
  • establises a setInterval() of 1000 milliseconds (1 second), whose function :
    • keeps count of how many times it has been called,
    • calls deferred.notify() every second until the counter i reaches the specified maximum x,
  • when the specified maximum is reached :
    • the interval, which would otherwise silently tick away ad infinitum, is cleared,
    • deferred.resolve() or deferred.reject() are called to settle the Deferred (and its promise),
function start(x) {
    console.log("Start: " + x);
    process(x).then(function(data) {
        console.log("Completed: " + data);
    }, function(data) {
        console.log("Cancelled: " + data);
    }, function(data) {
        console.log("In Progress: " + data);
    });
}

function process(x) {
    return $.Deferred(function(dfd) {
        var i = 1;
        var intervalRef = setInterval(function() {
            if(i < x) {
                dfd.notify(i++);
            } else {
                clearInterval(intervalRef);
                dfd[(x % 2 === 0)?'reject':'resolve'](x);
            }
        }, 1000);
    }).promise();
}

console.clear();
start(3);

Updated fiddle

Upvotes: 1

Related Questions