多个Q.all内部功能?

时间:2014-08-29 09:11:16

标签: javascript underscore.js promise q

我想向用户发送新书列表。到目前为止,下面的代码工作正常。问题是我不想多次发送一本书,所以我想过滤它们。

当前代码工作正常:

function checkActiveBooks(books) {
  var queue = _(books).map(function(book) {
    var deferred = Q.defer();

    // Get all alerts on given keywords
    request('http://localhost:5000/books?l=0&q=' + book.name, function(error, response, body) {
      if (error) {
        deferred.reject(error);
      }

      var books = JSON.parse(body);
      if (!_.isEmpty(books)) {

        // Loop through users of current book.
        var userBooks = _(book.users).map(function(user) {

            // Save object for this user with name and deals.
            return {
              user: user,
              book: book.name,
              books: books
            }

        });

        if (_.isEmpty(userBooks)) {
           deferred.resolve(null);
         } else {
           deferred.resolve(userBooks);
         }
      } else {
        deferred.resolve(null);
      }

    });

    return deferred.promise;

  });

  return Q.all(queue);
}

但现在我想过滤已发送的书籍:

function checkActiveBooks(books) {
    var queue = _(books).map(function(book) {
        var deferred = Q.defer();
        // Get all alerts on given keywords
        request('http://localhost:5000/books?l=0&q=' + book.name, function(error, response, body) {
            if (error) {
                deferred.reject(error);
            }
            var books = JSON.parse(body);
            if (!_.isEmpty(books)) {
                // Loop through users of current book.
                var userBooks = _(book.users).map(function(user) {
                    var defer = Q.defer();
                    var userBook = user.userBook.dataValues;
                    // Check per given UserBook which books are already sent to the user by mail
                    checkSentBooks(userBook).then(function(sentBooks) {
                        // Filter books which are already sent.
                        var leftBooks = _.reject(books, function(obj) {
                            return sentBooks.indexOf(obj.id) > -1;
                        });
                        // Save object for this user with name and deals.
                        var result = {
                            user: user,
                            book: book.name,
                            books: leftBooks
                        }
                        return deferred.resolve(result);
                    });
                    return Q.all(userBooks);
                } else {
                    deferred.resolve(null);
                }
            });
            return deferred.promise;
        });
        return Q.all(queue);
    }

但上面的代码不起作用。它不会停止循环。我认为使用q.all两次是有意义的,因为它包含两个循环。但我想我做错了......

2 个答案:

答案 0 :(得分:2)

首先,你应该始终在最低级别进行宣传。你在这里复杂化并且有多个延迟。通常,在将API转换为承诺时,您应该只有延迟。承诺连锁和撰写,所以让我们这样做:)

var request = Q.nfbind(require("request")); // a promised version.

这可以使您在顶部的代码变为:

function checkActiveBooks(books) {
    return Q.all(books.map(function(book){
        return request('http://.../books?l=0&q=' + book.name)
               .get(1) // body
               .then(JSON.parse) // parse body as json
               .then(function(book){
                    if(_.isEmpty(book.users)) return null; 
                    return book.users.map(function(user){
                         return {user: user, book: book.name, books: books };
                    });
               });
    });
}

在我看来哪个更优雅。

现在,如果我们想通过谓词过滤它们,我们可以这样做:

function checkActiveBooksThatWereNotSent(books) {
      return checkActiveBooks(books).then(function(books){
          return books.filter(function(book){ 
                    return checkSentBooks(book.book); 
                 });
      });
}

值得一提的是,Bluebird库具有实用方法,如Promise#filterPromise#map,可以缩短此代码。

请注意,如果checkSentBook是异步的,则需要稍微修改代码:

function checkActiveBooksThatWereNotSent(books) {
      return checkActiveBooks(books).then(function(books){
          return Q.all(books.map(function(book){ // note the Q.all
                    return Q.all([book, checkSentBooks(book.book)]); 
                 })).then(function(results){ 
                    return results.filter(function(x){ return x[1]; })
                                  .map(function(x){ return x[0]; });
                 });
      });
}

就像我说的,使用不同的库,这看起来会更好。以下是Bluebird中代码的外观,它的速度也快了两个数量级,具有良好的堆栈跟踪和未处理拒绝的检测。为了娱乐和荣耀,我投入了ES6箭头和速记属性:

var request = Promise.promisify(require("request"));

var checkActiveBooks = (books) =>
    Promise.
    map(books, book => request("...&q=" + book.name).get(1)).
    map(JSON.parse).
    map(book => book.users.length ? 
        book.users.map(user => {user, books, book: book.name) : null))

var checkActiveBooksThatWereNotSent = (books) =>
     checkActiveBooks(books).filter(checkBookSent)

我觉得好多了。

答案 1 :(得分:0)

根据@ Benjamins的建议,以下是checkSentBooks返回承诺时代码的样子:

var request = Q.nfbind(require("request")); // a promised version.
function checkActiveBooks(books) {
    return Q.all(_(books).map(function(book) {
        // a callback with multiple arguments will resolve the promise with
        // an array, so we use `spread` here
        return request('http://localhost:5000/books?l=0&q=' + book.name).spread(function(response, body) {
            var books = JSON.parse(body);
            if (_.isEmpty(books)) return null;
            return Q.all(_(book.users).map(function(user) {
                return checkSentBooks(user.userBook.dataValues).then(function(sentBooks) {
//              ^^^^^^ return a promise to the array for `Q.all`
                    return {
                        user: user,
                        book: book.name,
                        books:  _.reject(books, function(obj) {
                            return sentBooks.indexOf(obj.id) > -1;
                        })
                    };
                });
            }));
        });
    }));
}