2

我刚刚开始学习 Node.js,我正在尝试一个同时查询 Factual API 和 Google maps API 的项目。我一直在将来自几个不同来源的代码放在一起,它正在变成一团糟的回调。此外,它目前使用 for 循环,这意味着循环中的异步 API 调用永远不会运行。如果有人能帮助我重构这段代码并告诉我如何让 HTTP 请求在循环中运行,我将不胜感激。

这是我当前的代码,其中出现错误“TypeError:无法读取未定义的属性'腿'”:

var auth = require('./auth');
var Factual = require('./node_modules/factual-api');
var factual = new Factual(auth.key, auth.secret);
var path = require('path');
var express = require('express');
app = express();

app.configure(function() {
    app.use(express.static(path.join(__dirname, 'public')));
});

var http = require("http");

app.get('/', function(req, res) {
    var placedata = "<link rel='stylesheet' type='text/css' href='default.css' /> <table>";
    factual.get('/t/restaurants-gb', {
        limit: 50,
        sort: "$distance:asc",
        geo: {
            "$circle": {
                "$center": [req.query.lat, req.query.lng],
                "$meters": 15000
            }
        }
    }, function(error, result) {


        for (var i = 0; i < result.data.length; i++) {
            var d = new Date();
            var seconds = (d.getTime() / 1000).toFixed(0);
            url = "http://maps.googleapis.com/maps/api/directions/json?mode=transit&origin=" + req.query.lat + "," + req.query.lng + "&destination=" + result.data[i].latitude + "," + result.data[i].longitude + "&sensor=false&departure_time=" + seconds;
            var request = http.get(url, function(response) { 
                var buffer = "",
                    data, route;

                response.on("data", function(chunk) {
                    console.log(buffer);
                    buffer += chunk;
                });

                response.on("end", function(err) {
                    data = JSON.parse(buffer);
                    route = data.routes[0];

                    console.log("Time: " + route.legs[0].duration.text);
                    placedata += "<tr><th align='left'>" + result.data[i].name + "</th><td>" + result.data[i].address + "</td><td>" + result.data[i].tel + "</td><td>" + result.data[i].$distance + " Metres" + "</td></tr>";
                });
            });
        }
        placedata += "</table>";
        res.send(placedata);
        console.log(">> Home");
    });

});

app.listen(process.env.PORT, process.env.IP, function() {
    console.log('Server is running');
});
4

2 回答 2

4

您收到的错误来自 google maps API rate limiter。如果您查看返回的结果,您可能会得到类似的结果。

{ routes: [], status: 'OVER_QUERY_LIMIT' }

您应该在访问之前检查以确保数组中的元素存在,以防止发生这种崩溃。

第一次重构通过

var auth = require('./auth');
var Factual = require('factual-api');
var factual = new Factual(auth.key, auth.secret);
var path = require('path');
var express = require('express');
var concat = require('concat-stream');
var http = require("http");
var async = require("async");

app = express();

app.configure(function() {
    app.use(express.static(path.join(__dirname, 'public')));
});


function getDirections(data, fn) {
    var url = "http://maps.googleapis.com/maps/api/directions/json?mode=transit&origin=" + data.originLat + "," + data.originLong + "&destination=" + data.destLat + "," + data.destLong + "&sensor=false&departure_time=" + data.time;
    http.get(url, function(response) { 
        response.pipe(concat(function(results) {
            fn(JSON.parse(results));
        })
    )});
}

app.get('/', function(req, res) {
    res.write("<table>");
    factual.get('/t/restaurants-gb', {
        limit: 10,
        sort: "$distance:asc",
        geo: {
            "$circle": {
                "$center": [req.query.lat, req.query.lng],
                "$meters": 15000
            }
        }
    }, function(err, result) {
        async.eachSeries(result.data, function(data, fn) {
            var d = new Date();
            var seconds = (d.getTime() / 1000).toFixed(0);
            var directionData = { originLat: req.query.lat, originLong: req.query.lng, destLat: data.latitude, destLong: data.longitude, time: seconds };

            function writeEntry(directions) {
                console.log(directions);
                if(directions.routes.length == 0) {
                    setTimeout(function() {
                        getDirections(directionData, writeEntry);
                    }, 500);
                    return;
                }
                route = directions.routes[0];
                res.write("<tr><th align='left'>" + data.name + "</th><td>" + data.address + "</td><td>" + data.tel + "</td><td>" + data.$distance + " Metres" + "</td><td>" + route.legs[0].duration.text + "</td></tr>");
                fn();            
            }

            getDirections(directionData, writeEntry);
        }, function(err) {
            res.end("</table>");    
        });
    });
});

app.listen(process.env.PORT, process.env.IP, function() {
    console.log('Server is running');
});

我在这里做了一些重大的改变。

  1. 首先,我通过使用concat-stream摆脱了 google api 响应的手动缓冲。您将流指向它,一旦流完成写入,它就会将完整的响应传递给您。

  2. 我一有数据就将输出从缓冲(使用placedata)更改为直接写出。这意味着页面将开始更快地显示,而不是等待所有结果返回并立即发送。

  3. 我用 async.eachSeries 替换了 for 循环。这消除了许多错误(关闭错误,在行之前写入表的末尾等)并简化了数据访问。

  4. 检查谷歌地图调用是否失败。如果是,请在 500 毫秒内重试。

认为这是最终版本。这段代码仍然有很多错误,但它至少让你更接近你想要做的事情。您应该获得用于 Google 地图的开发人员密钥。这应该使您能够更快地拨打电话而不会出错。

于 2013-08-13T22:21:08.717 回答
1

这是一个(未经测试的)重构。我正在使用该async库,特别是async.map.

异步文档

现在发送谷歌地图请求的循环有一个回调。使用传统的 for 循环或 Array.forEach,没有异步回调,因此当您的异步任务完成后,您将不得不滚动自己的方式来执行某些操作。

除了用 替换 for 循环之外async.map,我做的最重要的事情是将一些匿名函数拆分为命名函数,以帮助减少回调地狱。所有重要的部分都隐藏在里面getRestsPlusRoutes

比较长度app.get()。更容易理解正在发生的事情,因为在发送响应之前您只关心单级回调。

我还将表示逻辑(创建 HTML 标记)与数据逻辑(Factual 和 Google Maps API 调用)分开。现在,获取餐厅和路线的代码与数据一起回调,因此您可以将其重新用于其他功能在你的项目中的地方。

我重新缩进到两个空格,个人喜欢看更多的嵌套。

我将 factual 重命名results.datarests,因此更具描述性的变量名称有助于记录代码。

var async = require('async');
var auth = require('./auth');
var Factual = require('./node_modules/factual-api');
var factual = new Factual(auth.key, auth.secret);
var path = require('path');
var express = require('express');
app = express();

app.configure(function() {
  app.use(express.static(path.join(__dirname, 'public')));
});

var http = require("http");

app.get('/', function(req, res) {
  getRestsPlusRoutes(req, function(err, restsPlusRoutes){
    if(err){ 
      console.log(err);
      return res.send(500);
    };
    var pd = buildPlaceData(restsPlusRoutes);
    res.send(pd);
    console.log(">> Home");
  });
});


// You can mitigate callback hell by declaring your asynchronous functions
// outside the code which calls them
function getRestsPlusRoutes(req, callback){
  // we have to pass in req so we have access to req.query.lat etc
  factual.get(
  '/t/restaurants-gb', 
  {
    limit: 50,
    sort: "$distance:asc",
    geo: {
      "$circle": {
        "$center": [req.query.lat, req.query.lng],
        "$meters": 15000,
      },
    },
  }, 
  function(error, result) {
    var rests = result.data;
    async.mapLimit(
    rests,               // For each item in rests,
    1,                   // with max 1 concurrency,
    getDirections,       // call getDirections(rest, done)
    function(err, mappedAll){
      // this callback executed upon all getDirections done(null, mappedOne)
      // or on any getDirections done(err)
      // mappedAll is an array of JSON.parsed bodys from Google Maps API call
      if(err){
        return callback(err);
      };
      for(var i=0; i<rests.length; i++){
        // Attach the `.routes` property of the google map result 
        // to the factual restaurant object
        rests[i].routes = mappedAll[i].routes;
      };
      return callback(null, rests);
    }); // end of async.mapLimit function call, including inline callback declaration
  });

  // declare the iterator function within getRestsPlusRoutes 
  // to ensure `var url = ...` can access `req`
  function getDirections(rest, done){
    var d = new Date();
    var seconds = (d.getTime() / 1000).toFixed(0);
    var url = "http://maps.googleapis.com/maps/api/directions/json?mode=transit&origin=" + req.query.lat + "," + req.query.lng + "&destination=" + rest.latitude + "," + rest.longitude + "&sensor=false&departure_time=" + seconds;

    http.get(url, function(response) { 
      var buffer = "",
          data, route;

      response.on("data", function(chunk) {
        console.log(buffer);
        buffer += chunk;
      });

      response.on("end", function(err) {
        if(err){ return done(err) };
        data = JSON.parse(buffer);
        done(null, data);
        // for simplicity, the entire parsed response is passed to the iterator done()
      });
    });
  }; // end of getDirections iterator function

}; // end of getRestsPlusRoutes


function buildPlaceData(restsPlusRoutes){
  var placedata = "<link rel='stylesheet' type='text/css' href='default.css' /> <table>";

  for(var i=0; i < restsPlusRoutes.length; i++){
    var rest = restsPlusRoutes[i];
    var route = rest.routes[0];
    console.log("Time: " + route.legs[0].duration.text);
    placedata += "<tr><th align='left'>" + rest.name + "</th><td>" + rest.address + "</td><td>" + rest.tel + "</td><td>" + rest.$distance + " Metres" + "</td></tr>";
  };

  placedata += "</table>";
  return placedata;
};


app.listen(process.env.PORT, process.env.IP, function() {
  console.log('Server is running');
});
于 2013-08-13T22:05:06.357 回答