Update event won't fire for further transactions after an unhandled exception in an observer

Consider the following code:

const Y = require('yjs')

const doc = new Y.Doc()
const map = doc.getMap('map')

doc.on('update', () => {
  console.log('received update')
})

map.set('x', '1') // will cause update event

const throwingObserver = () => {
  throw new Error('Failure')
}

map.observe(throwingObserver)

try {
  map.set('y', '2') // no update event
} catch(err) {
  console.log('thrown', err)
}

map.set('z', '3') // no update event

console.log('end')

I am not sure if this should be handled on the client side or in Yjs. Perhaps Yjs should throw exceptions from observers after the transaction clean up, so they won’t break the sync process.

You are right. My intention was to implement observe- and observeDeep-events to be exception-safe. This is a bug.

I just fixed it - you can try it out in v13.0.0-102. All other events (e.g. doc.on('afterTransaction', function () {})) are not yet exception-safe. I will investigate if I either throw events and cleanup, or if I just catch and log errors instead of throwing them.

Since .on('eventname', ..) events are placed all over the codebase, it makes sense to log errors instead of throwing them, because properly cleaning up after each .emit call feels unfeasible.

Any input on this is welcome.

wow, thank you for the quick fix, I have already implemented exception-safe observers on the app side, but it is definitely a good move for Yjs and will improve user experience for other developers. I think that afterTransaction and other events not as often used as observers, so maybe it can be left as is for now.