2016-04-29 92 views
0

此路由器用於訪問特定列表。在這個例子中,comments是一個對象數組。當我插入這gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});comments對象它的成功完成。但在前端(我用玉)。我嘗試使用這個list.comments[i].gvUrl,它返回undefined。它甚至會在for循環中返回undefined!我究竟做錯了什麼?NodeJS對象返回未定義

router.get('/:id', function (req, res, next) { 
    List.findOne({listurl: req.params.id}, function (err, doc) { 
     var z = 0; 
     if (!err && doc != null) { 
      for (var i = 0; i < doc.comments.length; i++) { 
       User.findOne({Name: doc.comments[i].commenter}, function (err, data) { 
        if (data) { 
         doc.comments[z++].gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'}); 
        } else { 
         doc.comments[z++].gvUrl = 'noGravs'; 
        } 
       }); 
      } 

      //this line returns unfefined; 
       console.log(doc.comments[0].gvUrl); 

       res.render('list', { 
        appTitle: doc.Title, 
        list: doc 
       }); 
     } 
     else { 
      res.status(404).render('404', {appTitle: "Book not found"}); 
     } 
    }); 
}); 
+0

的console.log(DOC),並確保它不是不確定的本身。但是這在var z = 0之前; – Matt

+0

投票重新開放,因爲儘管dup解釋了爲什麼結果是「未定義」,但它沒有解釋如何在同一時間在飛行中進行許多操作以最好地解決這個特定問題。 – jfriend00

回答

1

你正在發起了一堆異步操作的for循環內,並且預計在for循環完成做所有的異步操作。但是,實際上,他們中沒有一個尚未完成,因此您的doc.comments陣列尚未填充。您正在嘗試在填充之前使用它,這就是爲什麼您會在您嘗試使用它的地方看到它。

解決此問題的最佳方法是學習如何使用Promise,然後使用Blubird的Promise.map()或ES6 Promise.all()等類似方法觸發多個請求,然後讓promise引擎告訴您何時完成所有請求。

轉換數據庫的短促的叫聲在使用的承諾,你可以手工編寫知道如下當一切都做:

手工編碼的回調實現

router.get('/:id', function (req, res, next) { 
    List.findOne({listurl: req.params.id}, function (err, doc) { 
     var doneCnt = 0; 
     if (!err && doc != null) { 
      for (var i = 0; i < doc.comments.length; i++) { 
       (function(index) { 
        User.findOne({Name: doc.comments[i].commenter}, function (err, data) { 
         ++doneCnt; 
         if (err) { 
          // need some form of error handling here 
          doc.comments[index].gvUrl = ""; 
         } else { 
          if (data) { 
           doc.comments[index].gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'}); 
          } else { 
           doc.comments[index].gvUrl = 'noGravs'; 
          } 
         } 
         // if all requests are done now 
         if (doneCnt === doc.documents.length) { 
          res.render('list', { 
           appTitle: doc.Title, 
           list: doc 
          }); 
         } 
        }); 
       })(i); 
      } 
     } 
     else { 
      res.status(404).render('404', {appTitle: "Book not found"}); 
     } 
    }); 
} 

此代碼識別關於異步操作的以下內容:

  1. 您正在觸發多個User.findOne()異步操作s在一個循環中。
  2. 這些異步操作可以按任意順序完成。
  3. 循環完成後,這些異步操作將不會完成。所有的循環已經完成了啓動操作。他們將在稍後完成一些不確定的時間。
  4. 要知道何時完成所有異步操作,它會保留一個計數器,以計數當計數達到啓動的總請求數時已完成並呈現頁面的數量。這就是「手動」方式,讓您知道何時完成所有工作。

藍鳥承諾實施

下面是它如何使用藍鳥少輝庫和轉換你的數據庫操作以支持承諾工作:

var Promise = require('bluebird'); 
// promisify the methods of the List and User objects 
var List = Promise.promisifyAll(List); 
var User = Promise.promisifyAll(User); 

router.get('/:id', function (req, res, next) { 
    List.findOneAsync({listurl: req.params.id}).then(function(doc) { 
     if (!doc) { 
      throw "Empty Document"; 
     } 
     return Promise.map(doc.comments, function(item, index, length) { 
      return User.findOneAsync({Name: item.commenter}).then(function(data) { 
       if (data) { 
        item.gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'}); 
       } else { 
        item.gvUrl = 'noGravs'; 
       } 
      }); 
     }).then(function() { 
      return doc; 
     }); 
    }).then(function(doc) { 
     res.render('list', { 
      appTitle: doc.Title, 
      list: doc 
     }); 
    }).catch(function(err) { 
     res.status(404).render('404', {appTitle: "Book not found"}); 
    }); 
} 

這裏是如何工作的:

  1. 加載藍鳥承諾庫
  2. UserList對象添加promisified方法,以便爲每個返回承諾的方法添加新版本。
  3. 致電List.findOneAsync()。方法名稱上的"Async"後綴表示.promisifyAll()添加的新方法。
  4. 如果沒有doc,那麼拋棄哪個將拒絕承諾,最後將在.catch()中處理。 Promise是異步拋出安全的,對於異步錯誤處理非常方便。
  5. 致電Promise.map()doc.comments。這將自動迭代doc.comments數組並在數組中的每個項目上調用一個迭代器(類似於Array.prototype.map(),不同之處在於它收集了迭代器返回的所有promise,並返回一個新的promise,在所有基礎promise被解析時解析。方式,它允許所有的迭代器並行運行,並告訴你,當所有的迭代完成。
  6. 迭代器調用User.findOneAsync()並設置doc.comments[index].gvUrl值與結果。
  7. 有一個在Promise.map()只是一個額外的.then()處理器將該承諾的解決價值更改爲doc對象,以便我們可以從外部承諾處理程序處獲取該承諾
  8. 對於來自外部承諾的成功,渲染。
  9. 對於來自外部承諾的錯誤,請顯示404頁面。請記住,在整個計劃的任何地方,任何被拒絕的承諾都會傳播併成爲最高層的拒絕。承諾中異步錯誤的自動傳播非常有用。

ES6承諾實施

這可能與直ES6承諾完成,無需藍鳥承諾庫,但你必須用手工做一些事情:

  1. 您必須提醒List.findOne()操作。
  2. 您必須提示User.findOne()操作。
  3. 您必須使用常規doc.comments.map()迭代,並將每個單獨的承諾收集到一個數組中,然後在該數組上使用Promise.all(),而不是讓Promise.map()爲您做所有這些。

下面的代碼:

// manually promisify findOne 
List.findOneAsync = function(queryObj) { 
    return new Promise(function(resolve, reject) { 
     List.findOne(queryObj, function(err, data) { 
      if (err) return reject(err); 
      resolve(data); 
     }); 
    } 
} 
User.findOneAsync = function(queryObj) { 
    return new Promise(function(resolve, reject) { 
     User.findOne(queryObj, function(err, data) { 
      if (err) return reject(err); 
      resolve(data); 
     }); 
    } 
} 

router.get('/:id', function (req, res, next) { 
    List.findOneAsync({listurl: req.params.id}).then(function(doc) { 
     if (!doc) { 
      throw "Empty Document"; 
     } 
     var promises = doc.comments.map(function(item, index) { 
      return User.findOneAsync({Name: item.commenter}).then(function(data) { 
       if (data) { 
        item.gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'}); 
       } else { 
        item.gvUrl = 'noGravs'; 
       } 
      }); 
     }); 
     return Promise.all(promises).then(function() { 
      return doc; 
     }); 
    }).then(function(doc) { 
     res.render('list', { 
      appTitle: doc.Title, 
      list: doc 
     }); 
    }).catch(function(err) { 
     res.status(404).render('404', {appTitle: "Book not found"}); 
    }); 
} 
+0

你先生,太棒了! –

相關問題