我有一个Node.js应用程序,在初始化时,从SQL数据库中读取两个表并重建它们在内存中的关系。它们用于同步查找(很少)变化的数据。
问题:即使应用程序报告成功加载数据,有时我也无法访问数据。
代码:
constants.js
module.exports = {
ready: function () { return false; }
};
var log = sysLog('core', 'constants')
, Geo = require('../models/geo.js');
var _ready = false
, _countries = []
, _carriers = [];
function reload() {
_ready = false;
var index = Object.create(null);
return Geo.Country.find().map(function (country) {
var obj = country.toPlainObject()
, id = obj.id;
delete obj.id;
index[id] = obj;
return Object.freeze(obj);
}).then(function (countries) {
log.debug('Loaded ' + countries.length + ' countries');
_countries = countries;
return Geo.Carrier.Descriptor.find().map(function (carrier) {
var obj = carrier.toPlainObject();
if (obj.country) {
obj.country = index[obj.country];
}
return Object.freeze(obj);
}).then(function (carriers) {
log.debug('Loaded ' + carriers.length + ' carriers');
_carriers = carriers;
});
}).finally(function () {
_ready = true;
});
}
reload().catch(function (err) {
log.crit({ message: 'Could not load constants', reason: err });
process.exit(-42);
}).done();
module.exports = {
reload : reload,
ready : function () { return _ready; },
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
utils.js
var log = sysLog('core', 'utils')
, constants = require('./constants');
module.exports = {
getCountryByISO: function(iso) {
if (!iso) {
return;
}
if ('string' != typeof iso) {
throw new Error('getCountryByISO requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
switch (iso.length) {
case 2:
return _.findWhere(constants.countries(), { 'iso2' : iso.toUpperCase() });
case 3:
return _.findWhere(constants.countries(), { 'iso3' : iso.toUpperCase() });
default:
throw new Error('getCountryByISO requires a 2 or 3 letter ISO code');
}
},
getCarrierByCode: function(code) {
if (!code) {
return;
}
if ('string' != typeof code) {
throw new Error('getCarrierByCode requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
return _.findWhere(constants.carriers(), { 'code' : code });
},
getCarrierByHandle: function(handle) {
if (!handle) {
return;
}
if ('string' != typeof handle) {
throw new Error('getCarrierByHandle requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
return _.findWhere(constants.carriers(), { 'handle' : handle });
}
};
用例
if (data.handle) {
carrier = utils.getCarrierByHandle(data.handle);
if (_.isEmpty(carrier)) {
throw new InternalError('Unknown carrier', { handle: data.handle });
}
}
发生了什么:记录所有错误;一旦我在日志中看到错误(即"未知运营商"),我会检查SQL数据库以查看它是否应该被识别。到目前为止一直如此,所以我检查调试日志以查看数据是否已加载。我总是看到"加载X国家"和#34;加载Y载体"正确的值,没有#34;无法加载常数"或任何其他类型的麻烦。
这种情况发生在我启动应用程序的大约10%时间并且问题仍然存在(即12小时后似乎没有消失)并且似乎无论输入如何都会发生,这让我认为数据没有被正确引用。
问题:
constants.js 有什么问题吗?或者我做的事情显然是错误的?我已尝试将其设置为周期性加载(即使我不知道在这种情况下会发生这种情况)。
为什么我(有时)无法访问我的数据?
我该怎么做才能找出错误?
有什么方法可以解决这个问题吗?还有什么我可以实现所需的行为吗?排除了 constants.js 中的数据的硬编码。
其他信息:
constants.reload()
实际上从来没有从 constants.js 之外调用。
constants.js 仅在 utils.js 中需要 d。
SQL访问是通过建立在knex.js和bluebird之上的内部库完成的;到目前为止,它一直非常稳定。
版本:
Node.js v0.10.33
下划线 1.7.0
bluebird 2.3.11
knex 0.6.22
答案 0 :(得分:0)
constants.reload()从未实际从外部调用 constants.js。
那是你的问题。 constants.reload()
从数据库中读取,这是一个非常有趣的过程。节点的require()
是一个同步过程。在constants.js
中需要utils.js
并且返回module.exports
值时,您的数据库查询仍在运行。并且在app.js
到达它从utils模块调用方法的点的任何时间点,该查询仍然仍然正在运行,从而导致错误。
你可以说要求utils.js有副作用,需要constants.js,它有执行数据库查询的副作用,它具有同时修改自由变量的副作用{{1} }和_countries
。
将_carriers
和_countries
初始化为未解决的承诺。让_carriers
解决它们。使utils.js api异步。
promises.js:
reload()
utils.js
// ...
var Promise = require('bluebird');
var countriesResolve
, carriersResolve;
var _ready = false
, _countries = new Promise(function (resolve) {
countriesResolve = resolve;
})
, _carriers = new Promise(function (resolve) {
carriersResolve = resolve;
});
function reload() {
_ready = false;
var index = Object.create(null);
return Geo.Country.find().map(function (country) {
// ...
}).then(function (countries) {
log.debug('Loaded ' + countries.length + ' countries');
countriesResolve(countries);
return Geo.Carrier.Descriptor.find().map(function (carrier) {
// ...
}).then(function (carriers) {
log.debug('Loaded ' + carriers.length + ' carriers');
carriersResolve(carriers);
});
}).finally(function () {
_ready = true;
});
}
reload().catch(function (err) {
log.crit({ message: 'Could not load constants', reason: err });
process.exit(-42);
}).done();
module.exports = {
reload : reload,
ready : function () { return _ready; },
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
用例:
getCarrierByHandle: function(handle) {
// ...
return constants.carriers().then(function (carriers) {
return _.findWhere(carriers, { 'handle' : handle });
});
}
此设计还将消除对utils.getCarrierByHandle(data.handle).then(function (carrier) {
if (_.isEmpty(carrier)) {
throw new InternalError('Unknown carrier', { handle: data.handle });
}
}).then(function () {
// ... next step in application logic
});
方法的需求。
或者,您可以在初始化时调用ready
并挂起所有可能相关的操作,直到它完成。这种方法也会淘汰constants.reload()
方法。
我能做些什么来弄清楚什么是错的?
你本可以分析你的日志,并观察到“已装载的X国家”和“已装载的Y载体”有时是在“未知载体”之后写的,这有助于您意识到ready
的成功是一种竞争条件。 / p>
答案 1 :(得分:0)
}).finally(function () {
_ready = true;
});
finally
中的代码将始终被调用,无论是否在promise链中抛出错误。此外,永远不会达到您的reload().catch(/* ... */)
子句,因为finally
会吞下错误。
Geo.Country.find()
或Geo.Carrier.Descriptor.find()
可能会引发错误,_ready
仍会设置为true
,并且您的国家和运营商未设置的问题会持续存在。
如果您在没有ready
调用的情况下设计系统,就不会出现此问题,正如我在上一篇文章中所述。希望这可以告诉您这里的问题真的超出finally
吞噬catch
。真正的问题是依赖副作用;自由变量的修改导致脆弱的系统,特别是涉及异步时。我强烈建议不要这样做。
答案 2 :(得分:0)
试试这个
var log = sysLog('core', 'constants');
var Geo = require('../models/geo.js');
var index;
var _countries;
var _carriers;
function reload() {
index = Object.create(null);
_countries = Geo.Country.find().map(function (country) {
var obj = country.toPlainObject();
var id = obj.id;
delete obj.id;
index[id] = obj;
return Object.freeze(obj);
});
_carriers = _countries.then(function(countries) {
return Geo.Carrier.Descriptor.find().map(function (carrier) {
var obj = carrier.toPlainObject();
if (obj.country) {
obj.country = index[obj.country];
}
return Object.freeze(obj);
});
});
return _carriers;
}
reload().done();
module.exports = {
reload : reload,
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};