我正在使用sequelize从数据库表中加载一些数据,并将其缓存在具有TTL的内存对象中,这样我就不会一遍又一遍地从数据库中读取数据。
// ffm.ts
import { Op } from 'sequelize';
import { State } from 'src/db/models/geo/state';
export class FFMStates {
private static lastRefreshed = new Date(0); // Initialize to start of epoch
private static TTL = 60 * 60; // 1 hour
public static DATA: Record<number, Array<string>>;
public static async load() {
FFMStates.lastRefreshed = new Date();
const currentYear = FFMStates.lastRefreshed.getFullYear();
const results = await State.findAll({
where: {
year: { [Op.gte]: currentYear - 2 }, // load 3 or 4 latest years
ffm: true,
},
order: [
['year', 'ASC'],
['code', 'ASC'],
],
});
FFMStates.DATA = results.reduce((acc: Record<number, string[]>, state) => {
if (!acc[state.year]) {
acc[state.year] = [];
}
acc[state.year].push(state.code);
return acc;
}, {});
}
public static async get() {
if ((new Date().getTime() - FFMStates.lastRefreshed.getTime()) / 1000 > FFMStates.TTL) {
await FFMStates.load();
}
return FFMStates.DATA;
}
}
然后我按如下方式使用该模块:
import { FFMStates } from 'src/constants/health/ffm';
const ffmStates = await FFMStates.get();
const currentYear = ffmStates[this.year]; // This throws an error the first time this code runs because ffmStates is undefined
我尝试使用
console.log()
语句进行调试。似乎当第一次调用 FFMStates.get()
时,我的代码开始运行 FFMStates.load()
但不等待它,并立即执行 if block
之后的下一行并返回 FFMStates.DATA
,此时尚未定义。我不知道为什么await FFMStates.load()
不等。
我看不出所提供的代码是如何导致错误的。我只能看到,如果这些调用在执行所需的时间内,则在您第二次(及后续)调用
.get()
时才会发生错误 State.findAll({
即如果
.findAll
需要 1 秒才能得到结果,那么 FFMStates.DATA
此时将是 undefined
,但 FFMStates.lastRefreshed
将已更新。因此,在 1 秒时间段内对 .get()
的任何调用都将返回仍未定义的 .DATA
尝试进行以下更改。
在您的班级中添加
private static inflight: Promise<any> | null = null;
在
.load
末尾添加
FFMStates.inflight = null;
像这样改变
.get()
:
public static async get() {
if (FFMStates.inflight || (new Date().getTime() - FFMStates.lastRefreshed.getTime()) / 1000 > FFMStates.TTL) {
if (!FFMStates.inflight) {
FFMStates.inflight = FFMStates.load();
}
await FFMStates.inflight;
}
return FFMStates.DATA;
}
另一种解决方法是将
FFMStates.DATA
变为 Promise
public static DATA: Promise<Record<number, Array<string>>>;
更改
.load
以返回 results.reduce
return results.reduce((acc: Record<number, string[]>, state: {year:number, code:string}) => {
并更改
.get
如下:
public static async get() {
if ((new Date().getTime() - FFMStates.lastRefreshed.getTime()) / 1000 > FFMStates.TTL) {
// DO NOT USE await here
FFMStates.DATA = FFMStates.load();
}
return FFMStates.DATA;
}
可以说更干净,但确实需要将
.DATA
更改为 Promise,这可能会影响您可能未显示的其他代码。