我刚刚开始学习Node.js,我正在尝试一个查询Factual API和Google maps API的项目。我一直在从几个不同的来源把代码放在一起,它变得一堆乱七八糟。此外,它目前使用for循环,这意味着循环中的异步API调用永远不会运行。如果有人能帮助我重构这段代码并且还告诉我如何让HTTP请求在循环中运行,我将非常感激。
这是我当前的代码,我收到错误“TypeError:无法读取未定义的属性'leg'”:
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');
});
答案 0 :(得分:4)
您收到的错误来自Google地图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');
});
我在这里做了一些重大改变。
首先,我使用concat-stream摆脱了谷歌api响应的手动缓冲。您将流指向它,一旦流通过写入,它将通过您完整的响应。
我将输出从缓冲(使用placementata)更改为只要有数据就直接写出。这意味着页面将更快地显示,而不是等待所有结果返回并立即发送。
我用async.eachSeries替换了for循环。这删除了许多错误(关闭错误,在行之前写入表的末尾等)并简化了数据访问。
检查Google地图调用是否失败。如果确实如此,请在500毫秒内再试一次。
我会不认为这是最终版本。这段代码仍有很多问题,但它至少让你更接近你想要做的事情。您应该获得与Google地图一起使用的开发人员密钥。这样可以让您更快地拨打电话而不会出错。
答案 1 :(得分:1)
这是一个(未经测试的)重构器。我使用的是async
库,特别是async.map
。
现在发送Google地图请求的循环有一个回调。使用传统的for循环或Array.forEach,没有异步回调,因此您必须以自己的方式在异步任务完成后执行某些操作。
除了用async.map
替换for循环之外,我做的最重要的事情是将一些匿名函数拆分为命名函数,以帮助减少回调地狱。所有重要位都隐藏在getRestsPlusRoutes
内。
比较app.get()
的长度。更容易理解发生了什么,因为在发送响应之前,您只关注单级回调。
我还从数据逻辑(Factual和Google Maps API调用)中分离了表示逻辑(创建HTML标记)。现在,获取餐馆和路线的代码回调数据,因此您可以重新调整功能它在你项目的其他地方。
我重新缩进到两个空格,个人偏好看到更多的嵌套。
我将事实results.data
重命名为rests
,因此更具描述性的变量名称有助于记录代码。
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');
});