11

I want to implement basic Deferred object without using jQuery. Here i will be implementing only done and fail callbacks, with resolve and reject functions. and ofCourse associating promise method with this function.

i am doing the following implementation in pure js (Edited) :

function Deferred() {
    var d = {};
    d.resolve = function() {
        d.done(arguments);
    }
    d.reject = function() {
        d.fail(arguments);
    }
    d.promise = function() {
        var x = {};
        x.done = function(args) {
            return args;
        }
        x.fail = function(args) {
            return args;
        }
        return x;
    }
    return d;
}


var v;

var setVal = function() {
    var d = new Deferred();
    setTimeout(function() {
        v = 'a value';
        d.resolve(this);
    }, 5000);
    return d.promise();
};

setVal().done(function() {
    console.log('all done :' + v);
});

But the above gives the error : Object #<Object> has no method 'fail'

I know the returned object 'd' of Deferred() function does not have method done(). And if i returns d.promise from Deferred() this will not have resolve and reject functions.

Please point out what error i am making to achieve the simple objective of Deferred object.

Here is the fiddle i am doing : http://jsfiddle.net/SyEmK/14/

codeofnode
  • 18,169
  • 29
  • 85
  • 142
  • If you create an instance with `new` then you should add the methods to the `prototype`. The constructor returns an instance, even if you `return d`. You may want `var d = Deferred()` – elclanrs Aug 07 '13 at 07:10
  • You don't have a method called done. If you look at your object, you have resolve, reject, and promise. setVal() returns an instance of your object with those 3 methods. – THEtheChad Aug 07 '13 at 07:14
  • jQuery isn't the only library to implement promises. – JayC Aug 07 '13 at 07:14
  • by mistake i had put the previous coed.. please review the new edited code.. – codeofnode Aug 07 '13 at 07:14
  • 1
    @JayC I want to implement in pure js. (just as a constraint, i have to follow).. – codeofnode Aug 07 '13 at 07:16
  • Wait for ES6, it'll be built-in ;-) – Alnitak Aug 07 '13 at 07:17
  • Well, now the problem is that you're calling d.done() which still doesn't have a method called done. You made another variable called x which has x.done() and you're not calling it. – THEtheChad Aug 07 '13 at 07:21
  • you can have a look at [this implementation](https://github.com/kriskowal/q) – Arun P Johny Aug 07 '13 at 07:32

3 Answers3

21
function Deferred(){
  this._done = [];
  this._fail = [];
}
Deferred.prototype = {
  execute: function(list, args){
    var i = list.length;

    // convert arguments to an array
    // so they can be sent to the
    // callbacks via the apply method
    args = Array.prototype.slice.call(args);

    while(i--) list[i].apply(null, args);
  },
  resolve: function(){
    this.execute(this._done, arguments);
  },
  reject: function(){
    this.execute(this._fail, arguments);
  }, 
  done: function(callback){
    this._done.push(callback);
  },
  fail: function(callback){
    this._fail.push(callback);
  }  
}


var v;

var setVal = function() {
    var d = new Deferred();
    setTimeout(function() {
        v = 'a value';
        d.resolve(this);
    }, 5000);
    return d;
};

setVal().done(function() {
    console.log('all done :' + v);
});
THEtheChad
  • 2,372
  • 1
  • 16
  • 20
  • 5
    If you want to come even closer to the jQuery behavior, you might want to add a "promise" function that returns an object with only "done" and "fail" (so that the calling function won't have access to resolving/rejecting). Also - you can return "this" from done/fail, to allow chaining. – lyosef Oct 19 '14 at 20:25
1

I think it's good to un-assign the callbacks after they are called to avoid memory leaks and also execute ones added later straight away.

function deferred() {
  let thens = []
  let catches = []

  let status
  let resolvedValue
  let rejectedError

  return {
    resolve: value => {
      status = 'resolved'
      resolvedValue = value
      thens.forEach(t => t(value))
      thens = [] // Avoid memleaks.
    },
    reject: error => {
      status = 'rejected'
      rejectedError = error
      catches.forEach(c => c(error))
      catches = [] // Avoid memleaks.
    },
    then: cb => {
      if (status === 'resolved') {
        cb(resolvedValue)
      } else {
        thens.unshift(cb)
      }
    },
    catch: cb => {
      if (status === 'rejected') {
        cb(rejectedError)
      } else {
        catches.unshift(cb)
      }
    },
  }
}

const d = deferred()

setTimeout(() => {
  d.resolve('good')
}, 1000)

// Will be called after 1s
d.then(value => console.log('#1 resolved!', value))

setTimeout(() => {
  // Will be called after 3s and executed right away as it's already resolved
  d.then(value => console.log('#2 resolved!', value))
}, 3000)
Dominic
  • 62,658
  • 20
  • 139
  • 163
1

I never used jquery Deferred, but in case anyone comes here looking for a clone of angularjs $q.defer(), all you have to do is this

function Defer() {
    const self = this;
    self.promise = new Promise((resolve, reject) => {
        self.resolve = resolve;
        self.reject = reject;
    });
}

//use it like this
const deferred = new Defer();
//then call deferred.resolve(), deferred.reject() and use deferred.promise
Alex
  • 583
  • 5
  • 18