我开始学习node.js.我在这里遇到了问题。我正在呼叫一个返回JSON的天气服务(下面的网址)。
http://api.wunderground.com/api/Your_key/conditions/q/CA/San_Francisco.json
我想在HTML页面上显示api的结果。
我编写了以下代码(getInfo模块)来检索并返回JSON。
var fbResponse;
module.exports = function (url) {
var http=require('http');
http.get(url, function(res) {
var body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
fbResponse = JSON.parse(body);
console.log("Got response: ", fbResponse.response);
});
}).on('error', function(e) {
console.log("Got error: ", e);
});
return fbResponse;
};
为了使用这个模块,我创建了一个test.js文件,如下所示:
var relay = require('./getInfo');
var url = 'http://api.wunderground.com/api/Your_key/conditions/q/CA/San_Francisco.json';
var response;
var x=relay(url);
console.log (x);
输出如下:
undefined // console.log(x) from test.js
Got response: { version: '0.1',
termsofService: 'http://www.wunderground.com/weather/api/d/terms.html',
features: { conditions: 1 } }
测试代码中的控制台输出首先运行,其中没有数据。 HTTP get稍后竞争并显示我需要的实际输出。
如何修改测试代码以进行阻塞调用,以使测试代码中的var x实际上具有JSON输出而不是未定义?
如果没有对getInfo模块的阻塞调用,我能否实现所需的结果?
答案 0 :(得分:5)
如您所知,node是异步的,因此http.get
和res.on('end', ..
的回调将在relay
函数执行后触发并返回。所以通常你不能从中返回结果。
您有几个选择:
将回调传递给relay
并使用:
module.exports = function (url, cb) {
http.get(url, function(res) {
var body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
cb(null, JSON.parse(body));
});
}).on('error', cb);
};
然后像这样使用它:
var relay = require('./getInfo');
relay(url, function (err, x) {
if (err) {
console.error('oohh i got a error: ', err)
}
console.log('oohh i got a response: ', x)
});
使用承诺。这与传递回调几乎相同。稍微不那么轻巧,但在组合不同的异步操作时,您将了解它们有多棒。对于一个异步调用,可能没有任何区别。我在这里使用q
。你也可以使用bluebird,这种方式更有效,但缺少q的糖。您可以阅读this article以了解为什么承诺在某些情况下比回调更清晰。
module.exports = function (url) {
var deferred = Q.defer();
http.get(url, function(res) {
var body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
deferred.resolve(JSON.parse(body));
});
}).on('error', function(e) {
deferred.reject(e);
});
return deferred.promise;
};
var relay = require('./getInfo');
relay(url).then(function responseHandler(x) {
console.log('my awesome response')
}, function errorHandler(err) {
console.error('got an error', err);
});
使用生成器。它是Ecmascript 6规范的一部分,它仅存在于节点v0.11.x及更高版本中。但这几乎就是你想要的。
根据过去的承诺示例,我们可以这样做:
Q.async(function *() {
var relay = require('./getInfo');
var x = yield relay(url);
console.log('my awesome response', x)
});
这几乎就是你想要的。您还可以使用co library
的回调解决方案来实现它:
co(function *() {
var relay = require('./getInfo');
var x = yield relay.bind(null, url);
console.log('my awesome response', x);
});
您也可以在上面的示例中使用node-fibers,这几乎就像生成器一样。
如果您想使用前沿Javascript,可以使用Async/Await代替带有承诺的生成器。
答案 1 :(得分:2)
您需要传入回调:
var http = require('http');
module.exports = function(url, cb) {
http.get(url, function(res) {
var body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
var resp, err;
try {
resp = JSON.parse(body);
} catch (ex) {
err = ex;
}
cb(err, resp);
});
}).on('error', function(e) {
console.log("Got error: ", e);
cb(e);
});
};
然后使用它:
var relay = require('./getInfo');
var url = 'http://api.wunderground.com/api/Your_key/conditions/q/CA/San_Francisco.json';
var response;
relay(url, function(err, resp) {
if (err) throw err; // TODO: handle better
console.dir(resp);
});
答案 2 :(得分:0)
您可以在模块函数中进行回调以返回结果。
module.exports = function (url, onsuccess) {
...
res.on('end', function() {
fbResponse = JSON.parse(body);
if(onsuccess){
onsuccess(null, fbResponse);
}
然后在你的来电代码中:
relay(url, function(err, result){
console.log(result);
});
另一种选择是使用httpsync
模块,该模块提供同步 apis,以实现'http'模块提供的相同功能。但是在节点js编程中,你应该总是避免同步调用。