# Batch operations Batch operations allow multiple updates to be grouped into a single channel message and applied atomically. It ensures that all operations in a batch either succeed together or are discarded entirely. Batching is essential when multiple related updates to channel objects must be applied as a single atomic unit, for example, when application logic depends on multiple objects being updated simultaneously. Batching ensures that all operations in the batch either succeed or fail together. ## Create a batch context To batch object operations together, use the `batch()` method on a [PathObject](https://ably.com/docs/liveobjects/concepts/path-object) or [Instance](https://ably.com/docs/liveobjects/concepts/instance). This method accepts a callback function that receives a batch context that allows you to construct operations. Ably publishes these operations together as a single channel message. If an error occurs publishing the batched operations, all operations are discarded, preventing partial updates and ensuring atomicity. Call `batch()` on a `PathObject` to group operations on that path: ```javascript const myObject = await channel.object.get(); // Batch multiple operations on the channel object await myObject.batch((ctx) => { ctx.set('foo', 'bar'); ctx.set('baz', 42); ctx.remove('oldKey'); }); // Batch operations on a nested path await myObject.get('settings').batch((ctx) => { ctx.set('theme', 'dark'); ctx.set('fontSize', 14); ctx.set('notifications', true); }); // Batch operations on a counter await myObject.get('visits').batch((ctx) => { ctx.increment(5); ctx.increment(3); ctx.decrement(2); }); ``` You can only call `batch()` on an `PathObject` whose path resolves to a `LiveMap` or `LiveCounter` instance: ```javascript const myObject = await channel.object.get(); try { // Call batch() on 'username', which resolves to a string value await myObject.get('username').batch((ctx) => {}); } catch (err) { // Error 92007: Cannot batch operations on a non-LiveObject at path: username } ``` You can also call `batch()` on an `Instance` to batch operations on that specific object: ```javascript const settingsInstance = myObject.get('settings').instance(); if (settingsInstance) { await settingsInstance.batch((ctx) => { ctx.set('theme', 'dark'); ctx.set('fontSize', 14); ctx.remove('oldSetting'); }); } ``` ## Create objects in a batch You can create new objects inside a batch using `LiveMap.create()` and `LiveCounter.create()`. This allows you to atomically create and assign multiple objects in a single operation: ```javascript const myObject = await channel.object.get(); await myObject.batch((ctx) => { // Create and assign multiple objects atomically ctx.set('user', LiveMap.create({ name: 'Alice', score: LiveCounter.create(0), settings: LiveMap.create({ theme: 'dark', notifications: true }) })); ctx.set('visits', LiveCounter.create(0)); ctx.set('reactions', LiveMap.create({ likes: 0, hearts: 0 })); }); ``` Creating objects inside a batch ensures that all objects are created and assigned as a single atomic operation, preventing inconsistent intermediate states. ## Navigate a batch context Navigate to nested objects using the `get()` method on the batch context. Navigating a batch context with `get()` has the same behaviour as navigating an [`Instance`](https://ably.com/docs/liveobjects/concepts/instance#navigate): ```javascript await myObject.batch((ctx) => { // Navigate to nested paths and perform operations ctx.get('settings')?.set('theme', 'dark'); ctx.get('settings')?.get('preferences')?.set('language', 'en'); // Increment a nested LiveCounter instance ctx.get('visits')?.increment(5); }); ``` ## Cancel a batch To explicitly cancel a batch before it is applied, throw an error inside the batch function. This prevents any queued operations from being applied: ```javascript try { await myObject.batch((ctx) => { // Cancel the entire batch if a required value is missing const value = ctx.get('visits')?.value(); if (value === undefined) { throw new Error('visits key is missing'); } // Will not be applied if visits is missing ctx.set('lastSeen', Date.now()); }); } catch (err) { // Batch operations not applied if visits is missing } ``` ## Understand batch context behavior The batch context has the same API as [Instance](https://ably.com/docs/liveobjects/concepts/instance), except for `batch()` itself, but all mutation methods are synchronous and queue operations instead of sending them immediately. After the callback completes, all queued operations are sent together in a single channel message. ```javascript try { await myObject.batch((ctx) => { // These operations are synchronous and queued ctx.get('settings')?.set('theme', 'dark'); ctx.get('visits')?.increment(); }); // All operations published successfully } catch (err) { // Failed to publish operations, none of them were published } ``` Since the batch callback is synchronous, you can read current values inside a batch context without intermediate updates from other clients being applied between reads: ```javascript await myObject.batch((ctx) => { ctx.get('settings')?.get('theme')?.value(); // "dark" // no updates will be applied during execution of the batch callback ctx.get('settings')?.get('theme')?.value(); // "dark" }); ``` Operations on a batch context are not applied until they are all published, so you cannot read back data written inside the batch context callback: ```javascript await myObject.batch((ctx) => { // Get the value at the time batch() was called ctx.get('settings')?.get('theme')?.value(); // "dark" // Update the value ctx.get('settings')?.set('theme', 'light'); // The previous update will not be applied until the batch // callback completes, so the new value cannot be read back ctx.get('settings')?.get('theme')?.value(); // "dark" }); ``` The batch context object cannot be used outside the callback function. Attempting to do so results in an error: ```javascript let context; await myObject.batch((ctx) => { context = ctx; ctx.set('foo', 'bar'); }); // Calling any methods outside the batch callback will throw an error try { context.set('baz', 42); } catch (error) { // Error: Batch is closed } ``` When a batch operation is applied, the subscription is notified synchronously and sequentially for each operation included in the batch: ```javascript // Subscribe to the channel object myObject.subscribe(({ object, message }) => { console.log("Update:", message?.operation.action, message?.operation?.mapOp.key); }); // Perform a batch operation await myObject.batch((ctx) => { ctx.set('foo', 'bar'); ctx.set('baz', 42); ctx.set('qux', 'hello'); }); // Update: map.set foo // Update: map.set baz // Update: map.set qux ```