场景:用 Socket.io 和 MongoDB 做的 IM 系统,每对用户(聊天室)的聊天记录存在一个 这样的 Document 里:
{
"name": "1&2",
"msgs": [
{
"text": "...",
"uid": "...",
"from_id": 1,
"to_id": 2,
"date": "..."
}
]
}
复制代码
在对聊天记录进行增删查改时,首先要获取聊天室对应的文档,如果没有这个文档,就先创建一个新文档。
function delayFactory(delay: number) {
return new Promise(res => void setTimeout(res, delay))
}
interface Msg {
text: string
uid: string
from_id: string
to_id: string
date: string
}
interface RoomDoc {
name: string
msgs: Msg[]
}
const mongo = (function () {
const db: RoomDoc[] = []
return {
async getDoc(room_id: string) {
await delayFactory(50)
return db.find(doc => doc.name === room_id)
},
async createDoc(room_id: string) {
await delayFactory(1000)
const newRoomDoc = { name: room_id, msgs: [] }
db.push(newRoomDoc)
return newRoomDoc
},
printDB() {
console.log(db)
}
}
})()
async function getRoomDoc(room_id: string) {
return (await mongo.getDoc(room_id)) || (await mongo.createDoc(room_id))
}
getRoomDoc('1&2')
.then(console.log)
getRoomDoc('1&2')
.then(console.log)
.then(mongo.printDB)
复制代码
Bug:同一个聊天室建立了多个文档。
原因:前端忘记在组件销毁时忘记Socket.off('connect')
,组件反复加载后会在重连时触发多次绑定事件。后端的getRoomDoc
异步函数被反复调用,存在竞态问题。
虽然主要原因是前端的低级错误,但是这种情况却是真实存在的。当网络出现波动后,同一个聊天室内的客户端可能会同时重连,出现一样的问题。
我一开始尝试把getRoomDoc
变成防抖的函数,但其实防抖并不适用这种情况,因为难以设置合适的防抖时间。不如利用单例模式,让每个room_id
对应的Promise<RoomDoc>
在同一时间内只存在唯一实例,该Promise
在fulfill
后会把自己从缓存中删掉。
const getRoomDocSingleton = (function () {
const cache: Record<string, Promise<RoomDoc> > = Object.create(null)
return function (room_id: string) {
return cache[room_id] || (
cache[room_id] = getRoomDoc(room_id).then(result => {
delete cache[room_id]
return result
})
)
}
})()
getRoomDocSingleton('1&2')
.then(console.log)
getRoomDocSingleton('1&2')
.then(console.log)
.then(mongo.printDB)
复制代码
近期评论