cho45 / jsdeferred

Asynchronous library in JavaScript. Standalone and Compact.
http://cho45.stfuawsc.com/jsdeferred/
332 stars 48 forks source link

Add pause and end methods #20

Closed riophae closed 12 years ago

cho45 commented 12 years ago

Are there any concrete use cases for pause or end?

riophae commented 12 years ago

Pause is useful when you have to ask the user for some information that the process will continue with, or ask the user to do something before process resumed. End is a simple version of pause. It can be used for ending process when severe error occurs.

There is an example including two versions of code showing timeline: http://pastebin.com/QCbwx2Bs

You can see that the former is more clear and easy to control.

cho45 commented 12 years ago

Hmmm, I see. If I write code for the use case, I do like:

function confirmAuthorization () {
    var d = new Deferred();
    alert('You have to authorize this application before starting!');
    form.bind('submit', function () {
        d.call($(this).find('input[name=code]').val());
        return false;
    });
    return d;
}

next(function() {
  return http.get('http://example.com/get_access_token');
}).
error(function() {
  return confirmAuthorization();
}).
next(function(access_token) {
  // do someting with the access token we've got
  return http.get('http://example.com/api/get_timeline.json');
}).
error(function() {
  alert('Service unavailable. Check the network connection please.');
  this.cancel();
}).
next(function(data) {
  // process data and error may occur here
}).
error(function(e) {
  alert(e);
  this.cancel();
}).
next(function(processed_data) {
  // show timeline
});

Is this bad for you?

riophae commented 12 years ago

It works, but not for some other cases where you don't have (or want) a confirmAuthorization which returns a deferred. Pause is easier to use and understand, and works all the time.

  1. Cancel always breaks callback chain, which makes deferreds unreusable. For example:
var d = new Deferred;
d.
next(function(fun) {
  return fun();
}).
next(function(v) {
  // process data here
  // it's not stable and errors may occur too
  return v;
}).
error(function(e) {
  alert('An error occurs!');
  return this.cancel();
}).
next(function(v) {
  alert('Done: ' + v);
}).
error(function(e) {
  alert('An error occurs!');
  return this.cancel();
});

d.call(function() {
  throw 'error!';
});
// => 'An error occurs!'

d.call(function() {
  return 'everything is okay!';
});
// => nothing happened strangely

Just replace 'this.cancel()' with 'this.end()' and run it a second time.

d.call(function() {
  throw 'error!';
});
// => 'An error occurs!'

d.call(function() {
  return 'everything is okay!';
});
// => 'Done: everything is okay!'

This time it works fine! :)

  1. Sometimes we have different handlers to resolve kinds of problems. It can be like this:
next(function() {
  // do someting
  return 'some words';
}).
next(function(data) {
  switch (typeof data) {
    case 'string':
      // do something with data
      break;
    case 'object':
      // do something with data
      break;
    case 'number':
      // do something with data
      break;
  }
  return data;
}).
next(function(v) {
  alert('Result: ' + v);
});

To make the structure of code more clear, we can rewrite it by this way:

next(function() {
  // do someting
  return 'some words';
}).
next(function(v) {
  var d = new Deferred;
  switch (typeof v) {
    case 'string':
      handler1(d, v);
      break;
    case 'object':
      handler2(d, v);
      break;
    case 'number':
      handler3(d, v);
      break;
  }
  return d;
}).
next(function(v) {
  alert('Result: ' + v);
});

function handler1(d, data) {
  next(function() {
    // process data
    return data;
  }).
  next(function(v) {
    d.call(v);
  });
}

In this case, you have to pass the value and the deferred to every handler, and each of the handlers has to call back the deferred to continue the process. It's really ugly, isn't it? Now let's see what helps pause can provide.

next(function() {
  // do someting
  return 'some words';
}).
next(function(v) {
  return this.pause('wait', function() {
    switch (typeof v) {
      case 'string':
        handler1();
        break;
      case 'object':
        handler2();
        break;
      case 'number':
        handler3();
        break;
    }
  });
}).
next(function(v) {
  alert('Result: ' + v);
});

function handler1() {
  Deferred.paused['wait'].resume(function(data) {
    // process data
    return data;
  });
}

You can see that pause caches values and even statuses of deferreds. It's easy for you to continue the process with wanted ones and even then pause the process again! So pause makes it possible to create forkd deferreds. This can be very useful when organizing a complex logic.

riophae commented 12 years ago

Is this ignored?

cho45 commented 12 years ago

Hmm. I want to keep the core Deferred object small, so I'm not in the mood.

For example, If a function have resolve a problem which is not possible without it, I think more seriously.

But in this case, maybe there are more elegant way to resolve it without expand Deferred's prototype.

riophae commented 12 years ago

Thanks.