保存纪念日去掉 await 上报并加防重入,避免弱网下存出重复数据
onSubmit 原本 await requestSubscribe(),而它内部的 grant 上报走 api.request、 超时 10 秒。对已勾「总是保持以上选择」的用户连订阅弹窗都不会出现,点「保存」后 界面最长 10 秒毫无反馈;期间又没有任何提交中标志,重复点击会走两遍 storage.addAnniversary(),generateId() 每次生成新 id,直接存出两条重复纪念日。 改为:订阅授权 fire-and-forget(上报失败本就有 pending_sync_queue 兜底), onSubmit 去掉 async、加实例级 submitting 标志防重入,本地写失败时放开标志并提示。 手势铁律未破:onSubmit 由 bindtap 直接绑定,方法体去掉了 async,从入口到 requestSubscribe 之间只有 if 判断、解构和三个同步校验,无任何 await 或 then。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,10 @@ const lunar = require('../../utils/lunar')
|
|||||||
const subscribe = require('../../utils/subscribe')
|
const subscribe = require('../../utils/subscribe')
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
|
// 保存中标志。放在实例上而不是 data 里:它只用于防重入,不参与渲染,
|
||||||
|
// 走 setData 反而会多一次没必要的视图层通信。
|
||||||
|
submitting: false,
|
||||||
|
|
||||||
data: {
|
data: {
|
||||||
anniversaryId: null,
|
anniversaryId: null,
|
||||||
personId: null,
|
personId: null,
|
||||||
@@ -276,8 +280,21 @@ Page({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 提交
|
* 提交
|
||||||
|
*
|
||||||
|
* ⚠️ 本方法绝对不能加 async,方法体内在调用 requestSubscribe 之前也不能出现任何 await
|
||||||
|
* 或其他异步跳跃(Promise.then、wx 异步 API 的回调里再调等):
|
||||||
|
* wx.requestSubscribeMessage 必须由用户的真实点击手势同步触发,一旦中间断开,
|
||||||
|
* 手势上下文丢失,真机上必然报 "fail can only be invoked by user TAP gesture"。
|
||||||
|
* 从 bindtap="onSubmit" 进来到 requestSubscribe 之间,只有 setData 读取和几个同步校验。
|
||||||
*/
|
*/
|
||||||
async onSubmit() {
|
onSubmit() {
|
||||||
|
// 防重入。
|
||||||
|
// Why:保存链路本身是同步的,但保存成功后要等 1.5 秒的 toast 才 navigateBack,
|
||||||
|
// 这段时间用户完全可能再点一次「保存」;而 storage.addAnniversary 每次都会
|
||||||
|
// generateId() 生成一个新 id,重复点击的结果就是存出两条一模一样的纪念日。
|
||||||
|
// 注:这一句是同步的 if 判断,不影响下面订阅调用的手势上下文。
|
||||||
|
if (this.submitting) return
|
||||||
|
|
||||||
const { formData, anniversaryId, inputName } = this.data
|
const { formData, anniversaryId, inputName } = this.data
|
||||||
let { personId } = this.data
|
let { personId } = this.data
|
||||||
|
|
||||||
@@ -300,21 +317,26 @@ Page({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果开启了提醒,请求订阅消息授权
|
// 如果开启了提醒,请求订阅消息授权。
|
||||||
// 注:requestSubscribe 委托给 utils/subscribe.js 的 requestAndReport,
|
// 这里是 fire-and-forget,故意不 await:
|
||||||
// 该函数只会 resolve(true=同意/false=拒绝),不会 reject;
|
// 1) await 会切断手势上下文,订阅调用必然失败(见方法头注释);
|
||||||
// 无论用户是否同意,都不阻塞后续保存流程
|
// 2) requestAndReport 内部的 grant 上报走 api.request,超时长达 10 秒。对已勾选
|
||||||
|
// 「总是保持以上选择」的用户连订阅弹窗都不会出现,await 会让用户点完「保存」后
|
||||||
|
// 最长干等 10 秒、界面毫无反馈;
|
||||||
|
// 3) 上报失败本来就有 pending_sync_queue 兜底,下次启动会自动 flush,等它没有意义。
|
||||||
|
// requestAndReport 只 resolve、不 reject,所以这里不挂 catch 也不会产生未处理拒绝。
|
||||||
if (formData.remindEnabled) {
|
if (formData.remindEnabled) {
|
||||||
const accepted = await this.requestSubscribe()
|
this.requestSubscribe()
|
||||||
if (!accepted) {
|
|
||||||
console.log('用户未同意订阅消息,本次不上报额度')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 订阅调用已经发出,后面就没有手势上下文的顾虑了,可以安全地置位防重入标志
|
||||||
|
this.submitting = true
|
||||||
|
|
||||||
// 新增模式且没绑定 personId → 按姓名查找或自动创建
|
// 新增模式且没绑定 personId → 按姓名查找或自动创建
|
||||||
if (!anniversaryId && !personId) {
|
if (!anniversaryId && !personId) {
|
||||||
const person = storage.ensurePerson(name)
|
const person = storage.ensurePerson(name)
|
||||||
if (!person) {
|
if (!person) {
|
||||||
|
this.submitting = false
|
||||||
wx.showToast({ title: '保存失败', icon: 'none' })
|
wx.showToast({ title: '保存失败', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -351,6 +373,10 @@ Page({
|
|||||||
|
|
||||||
wx.showToast({ title: '保存成功', icon: 'success' })
|
wx.showToast({ title: '保存成功', icon: 'success' })
|
||||||
setTimeout(() => wx.navigateBack(), 1500)
|
setTimeout(() => wx.navigateBack(), 1500)
|
||||||
|
} else {
|
||||||
|
// 写本地失败:必须放开防重入标志,否则用户连重试的机会都没有
|
||||||
|
this.submitting = false
|
||||||
|
wx.showToast({ title: '保存失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 新增模式
|
// 新增模式
|
||||||
@@ -366,6 +392,10 @@ Page({
|
|||||||
|
|
||||||
wx.showToast({ title: '添加成功', icon: 'success' })
|
wx.showToast({ title: '添加成功', icon: 'success' })
|
||||||
setTimeout(() => wx.navigateBack(), 1500)
|
setTimeout(() => wx.navigateBack(), 1500)
|
||||||
|
} else {
|
||||||
|
// 同上:写本地失败要放开标志,让用户能重试
|
||||||
|
this.submitting = false
|
||||||
|
wx.showToast({ title: '保存失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user