1

How do I use defer correctly? I've got two functions here, in which one defer is used correctly? In which one it is used incorrectly? And why resp. why not?

First example:

getFoo1: function() {
  var dfd = $q.defer();

  db.allDocs({include_docs: true}, function(err, response) {
    if(err) {
      console.log(err);
      dfd.reject(err);
    } else {
      var qcs = [];
      for(var i=0; i < response.total_rows; i++) {
        if(response.rows[i].doc.type == 'bar') {

          var qc = {id: response.rows[i].doc._id,
            isFoo: true
          };

          oneFunction(qc)
          .then(anotherFunction(qc))
          .then(qcs.push(qc));
        }
      }
      dfd.resolve(qcs);
    }
  });
  return dfd.promise;
},

Second example:

getFoo2: function() {
  var dfd = $q.defer();

  db.allDocs({include_docs: true}, function(err, response) {
    if(err) {
      dfd.reject(err);
    } else {
      dfd.resolve(response);
    }
  });

  return dfd.promise
  .then(function(response) {
    var foo = [];
    for(var i=0; i < response.total_rows; i++) {
      if(response.rows[i].doc.type == 'bar') {
        var qc = {id: response.rows[i].doc._id,
          isFoo: true
        };

        return oneFunction(qc)
        .then(anotherFunction(qc))
        .then(foo.push(qc));
      }   
    }
  }, function(err){
     console.log(err);
  });
},

The oneFunction does nothing asynchronously.

The anotherFunction does something asynchronously (retrieving data from an external database).

EDIT: Thanks to @Bergi the correct function should look like this:

getFoo3: function() {
  var dfd = $q.defer();

  db.allDocs({include_docs: true}, function(err, response) {
    if(err) {
      dfd.reject(err);
    } else {
      dfd.resolve(response);
    }
  });

  return dfd.promise
  .then(function(response) {
    var foos = [];

  for (var i=0; i < response.total_rows; i++) {
    if (response.rows[i].doc.type == 'bar') {
      var qc = {id: response.rows[i].doc._id,
        isFoo: true
      };
      var foo = oneFunction(qc);
      foos.push(foo);
    }
  }

  var promises = foos.map(anotherFunction); // make a promise for each foo
  return $q.all(promises);

  }, function(err){
     console.log(err);
  });
},
thadeuszlay
  • 2,787
  • 7
  • 32
  • 67
  • to `then` you should pass reference to function, but instead you instant call function like `anotherFunction(qc)`. So seems in both it used incorrectly :-) – Grundy Jul 01 '15 at 13:41
  • Could you elaborate on your comment, please? At which `then` part? How should the function looks like instead, @Grundy? – thadeuszlay Jul 01 '15 at 13:46
  • if you explain what you expected, i can try explain how it should. Anyway i mean `.then(anotherFunction(qc))` should be like `.then(function(){anotherFunction(qc)})` until `anotherFunction` not return function – Grundy Jul 01 '15 at 13:48
  • How does `anotherFunction()` gets it's parameter `qc`, @Grundy? – thadeuszlay Jul 01 '15 at 13:51
  • it take by closure :-) – Grundy Jul 01 '15 at 13:52
  • but here a ugly sample, because in js variable scope on function level, so always passed last value – Grundy Jul 01 '15 at 13:54
  • so here seems more right: `.then((function(a){return function(){anotherFunction(a)}})(qc))` :-) or simple `.then(anotherFunction.bind(null, qc))` – Grundy Jul 01 '15 at 13:56
  • what is `a`, @Grundy? – thadeuszlay Jul 01 '15 at 14:01
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/82098/discussion-between-thadeuszlay-and-grundy). – thadeuszlay Jul 01 '15 at 14:07
  • Why it's not `.then(function() {return function(qc){anotherFunction(qc)}})`, @Grundy – thadeuszlay Jul 01 '15 at 14:18
  • Both examples are incorrect. You're using [tag:pouchdb] in your example, Pouch already returns promises - so the correct usage pattern would be to use the promises it returns and work with them and then wrap it in a `$q.when`. I can elaborate in chat. – Benjamin Gruenbaum Jul 01 '15 at 17:29
  • @BenjaminGruenbaum: Yeah, sure. :) Please elaborate that in chat. – thadeuszlay Jul 01 '15 at 17:30

1 Answers1

2

You've used $q.defer correctly in the second example[1] - creating a promise for the response of the db.allDocs asynchronous call (and nothing else). Altough pochdb seems to already return promises, as @Benjamin mentions in the comments, so it's unnecessary (but not wrong).

The first example is just a mess, convoluting the construction of the promise with the error logging and that ominous loop.

1: Except for dfd.promise(), which is not a function but a property. Go for dfd.promise.then(…).


However, that loop is a very different topic, and seems to be wrong in both snippets. Some points:

  • In the second snippet, your return from the callback function in the body of the loop, right on the first iteration that fulfills the predicate.
  • If oneFunction(qc) is not asynchronous, it doesn't need to (read: shouldn't) return a promise - or if it does not, that .then(…) call is wrong.
  • anotherFunction(qc) seems to return a promise (it's asynchronous as you say). But you must not pass that promise to .then(), a callback function is expected there!
  • Same thing for foo.push(qc) - it's not a callback function that you would pass to .then().
  • After all, you are doing something asynchronous in that loop. Here, you have to use Promise.all now!

If I had to bet, I'd say you need

.then(function(response) {
  var foos = [];
  for (var i=0; i < response.total_rows; i++) {
    if (response.rows[i].doc.type == 'bar') {
      var qc = {id: response.rows[i].doc._id,
        isFoo: true
      };
      var foo = oneFunction(qc);
      foos.push(foo);
    }
  }
  var promises = foos.map(anotherFunction); // make a promise for each foo
  return $q.all(promises);
})
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • response.rows can be `map`d since it's an array, other than that looks good :) – Benjamin Gruenbaum Jul 01 '15 at 18:35
  • Yeah, `filter`ed and `map`d, but I wanted to stay close to the original code. – Bergi Jul 01 '15 at 18:40
  • @thadeuszlay: No, for the predicate `response.rows[i].doc.type == 'bar'` a standard [Array `filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) will suffice. – Bergi Jul 07 '15 at 14:07