如何用 setInterval() 每10秒检查支付状态并停止轮询?
支付老李
结论
把定时器句柄保存在当前支付订单对应的变量中,检测到 PAID 后调用 clearInterval(timer)。由于支付状态查询是异步操作,直接使用 setInterval() 可能导致请求重叠。更稳妥的做法是递归调用 setTimeout(),等本次查询结束后再安排下一次查询。
推荐实现:递归 setTimeout()
async function pollPaymentStatus({ billId, message, nick }) {
let stopped = false;
const checkPayment = async () => {
if (stopped) return;
try {
const data = await qiwiApi.getBillInfo(billId);
if (data.status.value === 'PAID') {
stopped = true;
const Rcon = require('rcon');
const conn = new Rcon('**');
conn
.on('auth', function () {
console.log('Authenticated');
console.log('Sending command: SPONSOR!');
conn.send(`**`);
})
.on('response', function (str) {
console.log(`Response: ${str}`);
})
.on('error', function (err) {
console.error('RCON error:', err);
})
.on('end', function () {
console.log('Connection closed');
});
conn.connect();
const embed = new Discord.MessageEmbed()
.setAuthor(
'GUARD QIWI',
'https://media.discordapp.net/attachments/899699390082601011/903958418166005760/871755380349341797.png'
)
.setDescription('Ваш счет оплачен!');
await message.author.send({ embeds: [embed] });
return;
}
console.log(`${message.author.id} еще не оплатил счет.`);
} catch (error) {
console.error(
`Не удалось проверить счет ${billId}:`,
error
);
}
if (!stopped) {
setTimeout(checkPayment, 10_000);
}
};
await checkPayment();
}
创建账单并发送支付链接后,启动轮询:
await message.author.send({ embeds: [PAYID] });
pollPaymentStatus({
billId: params.billId,
message,
nick
}).catch(console.error);
这段代码会先立即查询一次。若支付尚未完成,就在 10 秒后再次查询。状态变为 PAID 后,函数会直接返回,不再设置新的定时器。
如果必须使用 setInterval()
需要保存 setInterval() 返回的句柄,并将这个句柄传给 clearInterval():
let checking = false;
const timer = setInterval(async () => {
// 防止上一次异步查询尚未结束时再次发起请求
if (checking) return;
checking = true;
try {
const data = await qiwiApi.getBillInfo(params.billId);
if (data.status.value === 'PAID') {
clearInterval(timer);
const embed = new Discord.MessageEmbed()
.setAuthor(
'GUARD QIWI',
'https://media.discordapp.net/attachments/899699390082601011/903958418166005760/871755380349341797.png'
)
.setDescription('Ваш счет оплачен!');
await message.author.send({ embeds: [embed] });
// 在这里执行支付成功后的 RCON 操作
return;
}
console.log(`${message.author.id} еще не оплатил счет.`);
} catch (error) {
console.error(
`Не удалось проверить счет ${params.billId}:`,
error
);
} finally {
checking = false;
}
}, 10_000);
clearInterval() 没有生效,通常有以下几种原因:
- 没有保存
setInterval()返回的句柄。 - 传给
clearInterval()的是另一个定时器句柄。 - 定时器变量超出了当前可访问的作用域。
- 每次收到消息时都创建了新定时器,但只清除了其中一个。
- 异步回调已经开始执行。
clearInterval()只能阻止后续调度,无法中止已经发出的网络请求。
建议增加超时机制
如果用户始终没有支付,轮询不应一直运行。可以设置一个截止时间,例如 15 分钟:
async function pollPaymentStatus({ billId, message }) {
const deadline = Date.now() + 15 * 60 * 1000;
const checkPayment = async () => {
if (Date.now() >= deadline) {
await message.author.send('Срок ожидания оплаты истек.');
return;
}
try {
const data = await qiwiApi.getBillInfo(billId);
if (data.status.value === 'PAID') {
await message.author.send('Ваш счет оплачен!');
return;
}
} catch (error) {
console.error(`Ошибка проверки счета ${billId}:`, error);
}
setTimeout(checkPayment, 10_000);
};
await checkPayment();
}
其他需要修正的地方
私信判断中的表达式存在运算符优先级问题:
if (!message.channel.name === 'DM')
程序会先计算 !message.channel.name,然后再把结果与 'DM' 比较,所以这个判断几乎不可能得到预期结果。应按照当前使用的 discord.js 版本判断频道类型。旧版本中可能这样写:
if (message.channel.type !== 'DM') {
return message.channel.send(
'Эта команда работает только в личных сообщениях бота!'
);
}
频道类型的具体取值由 discord.js 版本决定,应以项目当前版本的 API 为准。
RCON 连接结束时也不应调用:
process.exit();
这行代码会终止整个 Discord Bot 进程,而不是只结束当前这次支付处理。正常关闭 RCON 连接即可。
生产环境中还应使用 billId 作为键保存订单状态,防止同一账单被重复处理。轮询可能因进程重启、并发请求或网络延迟而多次读到 PAID,因此发放权益的操作需要保持幂等。
备注:内容仅供参考。
