Skip to content

Commit 341d756

Browse files
tyao1meta-codesync[bot]
authored andcommitted
Add an option to skip notify in read time batch upddate
Reviewed By: yczhu Differential Revision: D86567923 fbshipit-source-id: 8709163491d4cd0618288e959e8f13037f39385d
1 parent 06aff15 commit 341d756

File tree

3 files changed

+207
-76
lines changed

3 files changed

+207
-76
lines changed

packages/relay-runtime/store/RelayModernStore.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,22 @@ class RelayModernStore implements Store {
236236
}
237237
}
238238

239+
batchLiveStateUpdatesWithoutNotify(callback: () => void): boolean {
240+
if (this.__log != null) {
241+
this.__log({name: 'liveresolver.batch.start'});
242+
}
243+
let hasPublished = false;
244+
try {
245+
hasPublished =
246+
this._resolverCache.batchLiveStateUpdatesWithoutNotify(callback);
247+
} finally {
248+
if (this.__log != null) {
249+
this.__log({name: 'liveresolver.batch.end'});
250+
}
251+
}
252+
return hasPublished;
253+
}
254+
239255
check(
240256
operation: OperationDescriptor,
241257
options?: CheckOptions,

packages/relay-runtime/store/__tests__/resolvers/LiveResolvers-test.js

Lines changed: 166 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,7 @@ test('unsubscribe happens when record is updated due to missing data', () => {
122122
expect(__debug.state.subscribers.size).toBe(1);
123123
});
124124

125-
test('Updates can be batched', () => {
126-
const source = RelayRecordSource.create({
127-
'client:root': {
128-
__id: 'client:root',
129-
__typename: '__Root',
130-
},
131-
});
125+
describe('batching', () => {
132126
const operation = createOperationDescriptor(
133127
graphql`
134128
query LiveResolversTestBatchingQuery {
@@ -140,95 +134,191 @@ test('Updates can be batched', () => {
140134
`,
141135
{},
142136
);
143-
const log = jest.fn();
144-
const store = new RelayModernStore(source, {
145-
gcReleaseBufferSize: 0,
146-
log,
147-
});
148-
const environment = new RelayModernEnvironment({
149-
network: RelayNetwork.create(jest.fn()),
150-
store,
151-
log,
152-
});
137+
test('Updates can be batched', () => {
138+
const source = RelayRecordSource.create({
139+
'client:root': {
140+
__id: 'client:root',
141+
__typename: '__Root',
142+
},
143+
});
153144

154-
function getBatchLogEventNames(): string[] {
155-
return log.mock.calls
156-
.map(log => log[0].name)
157-
.filter(name => {
158-
return name.startsWith('liveresolver.batch');
159-
});
160-
}
145+
const log = jest.fn();
146+
const store = new RelayModernStore(source, {
147+
gcReleaseBufferSize: 0,
148+
log,
149+
});
150+
const environment = new RelayModernEnvironment({
151+
network: RelayNetwork.create(jest.fn()),
152+
store,
153+
log,
154+
});
161155

162-
const snapshot = environment.lookup(operation.fragment);
156+
function getBatchLogEventNames(): string[] {
157+
return log.mock.calls
158+
.map(log => log[0].name)
159+
.filter(name => {
160+
return name.startsWith('liveresolver.batch');
161+
});
162+
}
163163

164-
const handler = jest.fn<[Snapshot], void>();
165-
environment.subscribe(snapshot, handler);
164+
const snapshot = environment.lookup(operation.fragment);
166165

167-
expect(handler.mock.calls.length).toBe(0);
166+
const handler = jest.fn<[Snapshot], void>();
167+
environment.subscribe(snapshot, handler);
168168

169-
// Update without batching
170-
GLOBAL_STORE.dispatch({type: 'INCREMENT'});
169+
expect(handler.mock.calls.length).toBe(0);
171170

172-
// We get notified once per live resolver. :(
173-
expect(handler.mock.calls.length).toBe(2);
171+
// Update without batching
172+
GLOBAL_STORE.dispatch({type: 'INCREMENT'});
174173

175-
let lastCallCount = handler.mock.calls.length;
174+
// We get notified once per live resolver. :(
175+
expect(handler.mock.calls.length).toBe(2);
176176

177-
expect(getBatchLogEventNames()).toEqual([]);
177+
let lastCallCount = handler.mock.calls.length;
178178

179-
// Update _with_ batching.
180-
store.batchLiveStateUpdates(() => {
181-
GLOBAL_STORE.dispatch({type: 'INCREMENT'});
179+
expect(getBatchLogEventNames()).toEqual([]);
180+
181+
// Update _with_ batching.
182+
store.batchLiveStateUpdates(() => {
183+
GLOBAL_STORE.dispatch({type: 'INCREMENT'});
184+
});
185+
186+
expect(getBatchLogEventNames()).toEqual([
187+
'liveresolver.batch.start',
188+
'liveresolver.batch.end',
189+
]);
190+
191+
// We get notified once per batch! :)
192+
expect(handler.mock.calls.length - lastCallCount).toBe(1);
193+
194+
lastCallCount = handler.mock.calls.length;
195+
196+
// Update with batching, but update throws.
197+
// This might happen if some other subscriber to the store throws when they
198+
// get notified of an error.
199+
expect(() => {
200+
store.batchLiveStateUpdates(() => {
201+
GLOBAL_STORE.dispatch({type: 'INCREMENT'});
202+
throw new Error('An Example Error');
203+
});
204+
}).toThrowError('An Example Error');
205+
206+
expect(getBatchLogEventNames()).toEqual([
207+
'liveresolver.batch.start',
208+
'liveresolver.batch.end',
209+
'liveresolver.batch.start',
210+
'liveresolver.batch.end',
211+
]);
212+
213+
// We still notify our subscribers
214+
expect(handler.mock.calls.length - lastCallCount).toBe(1);
215+
216+
// Nested calls to batchLiveStateUpdate throw
217+
expect(() => {
218+
store.batchLiveStateUpdates(() => {
219+
store.batchLiveStateUpdates(() => {});
220+
});
221+
}).toThrow('Unexpected nested call to batchLiveStateUpdates.');
222+
223+
expect(getBatchLogEventNames()).toEqual([
224+
'liveresolver.batch.start',
225+
'liveresolver.batch.end',
226+
'liveresolver.batch.start',
227+
'liveresolver.batch.end',
228+
// Here we can see the nesting
229+
'liveresolver.batch.start',
230+
'liveresolver.batch.start',
231+
'liveresolver.batch.end',
232+
'liveresolver.batch.end',
233+
]);
182234
});
183235

184-
expect(getBatchLogEventNames()).toEqual([
185-
'liveresolver.batch.start',
186-
'liveresolver.batch.end',
187-
]);
236+
test('Updates can be batched without notify', () => {
237+
const source = RelayRecordSource.create({
238+
'client:root': {
239+
__id: 'client:root',
240+
__typename: '__Root',
241+
},
242+
});
243+
const log = jest.fn();
244+
const store = new RelayModernStore(source, {
245+
gcReleaseBufferSize: 0,
246+
log,
247+
});
248+
const environment = new RelayModernEnvironment({
249+
network: RelayNetwork.create(jest.fn()),
250+
store,
251+
log,
252+
});
188253

189-
// We get notified once per batch! :)
190-
expect(handler.mock.calls.length - lastCallCount).toBe(1);
254+
function getBatchLogEventNames(): string[] {
255+
return log.mock.calls
256+
.map(log => log[0].name)
257+
.filter(name => {
258+
return name.startsWith('liveresolver.batch');
259+
});
260+
}
191261

192-
lastCallCount = handler.mock.calls.length;
262+
const snapshot = environment.lookup(operation.fragment);
193263

194-
// Update with batching, but update throws.
195-
// This might happen if some other subscriber to the store throws when they
196-
// get notified of an error.
197-
expect(() => {
198-
store.batchLiveStateUpdates(() => {
264+
const handler = jest.fn<[Snapshot], void>();
265+
environment.subscribe(snapshot, handler);
266+
267+
expect(handler.mock.calls.length).toBe(0);
268+
269+
let lastCallCount = handler.mock.calls.length;
270+
271+
expect(getBatchLogEventNames()).toEqual([]);
272+
273+
// Update _with_ batching.
274+
store.batchLiveStateUpdatesWithoutNotify(() => {
199275
GLOBAL_STORE.dispatch({type: 'INCREMENT'});
200-
throw new Error('An Example Error');
201276
});
202-
}).toThrowError('An Example Error');
203277

204-
expect(getBatchLogEventNames()).toEqual([
205-
'liveresolver.batch.start',
206-
'liveresolver.batch.end',
207-
'liveresolver.batch.start',
208-
'liveresolver.batch.end',
209-
]);
278+
expect(getBatchLogEventNames()).toEqual([
279+
'liveresolver.batch.start',
280+
'liveresolver.batch.end',
281+
]);
210282

211-
// We still notify our subscribers
212-
expect(handler.mock.calls.length - lastCallCount).toBe(1);
283+
// We don't get notified
284+
expect(handler.mock.calls.length - lastCallCount).toBe(0);
213285

214-
// Nested calls to batchLiveStateUpdate throw
215-
expect(() => {
216-
store.batchLiveStateUpdates(() => {
217-
store.batchLiveStateUpdates(() => {});
286+
lastCallCount = handler.mock.calls.length;
287+
288+
// Update with batching, but update throws.
289+
// This might happen if some other subscriber to the store throws when they
290+
// get notified of an error.
291+
const _ = store.batchLiveStateUpdatesWithoutNotify(() => {
292+
GLOBAL_STORE.dispatch({type: 'INCREMENT'});
218293
});
219-
}).toThrow('Unexpected nested call to batchLiveStateUpdates.');
220-
221-
expect(getBatchLogEventNames()).toEqual([
222-
'liveresolver.batch.start',
223-
'liveresolver.batch.end',
224-
'liveresolver.batch.start',
225-
'liveresolver.batch.end',
226-
// Here we can see the nesting
227-
'liveresolver.batch.start',
228-
'liveresolver.batch.start',
229-
'liveresolver.batch.end',
230-
'liveresolver.batch.end',
231-
]);
294+
295+
expect(getBatchLogEventNames()).toEqual([
296+
'liveresolver.batch.start',
297+
'liveresolver.batch.end',
298+
'liveresolver.batch.start',
299+
'liveresolver.batch.end',
300+
]);
301+
302+
// Don't notify
303+
expect(handler.mock.calls.length - lastCallCount).toBe(0);
304+
305+
// Nested calls to batchLiveStateUpdate throw
306+
store.batchLiveStateUpdatesWithoutNotify(() => {
307+
store.batchLiveStateUpdatesWithoutNotify(() => {});
308+
});
309+
310+
expect(getBatchLogEventNames()).toEqual([
311+
'liveresolver.batch.start',
312+
'liveresolver.batch.end',
313+
'liveresolver.batch.start',
314+
'liveresolver.batch.end',
315+
// Here we can see the nesting
316+
'liveresolver.batch.start',
317+
'liveresolver.batch.start',
318+
'liveresolver.batch.end',
319+
'liveresolver.batch.end',
320+
]);
321+
});
232322
});
233323

234324
test('Errors thrown during _initial_ read() are caught as resolver errors', () => {

packages/relay-runtime/store/live-resolvers/LiveResolverCache.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,31 @@ class LiveResolverCache implements ResolverCache {
474474
}
475475
}
476476

477+
// Returns true if source is published
478+
batchLiveStateUpdatesWithoutNotify(callback: () => void): boolean {
479+
invariant(
480+
!this._handlingBatch,
481+
'Unexpected nested call to batchLiveStateUpdates.',
482+
);
483+
this._handlingBatch = true;
484+
try {
485+
callback();
486+
} finally {
487+
const shouldPublish = this._liveResolverBatchRecordSource != null;
488+
// We lazily create the record source. If one has not been created, there
489+
// is nothing to publish.
490+
if (shouldPublish) {
491+
// $FlowFixMe[incompatible-type] Null checked
492+
this._store.publish(this._liveResolverBatchRecordSource);
493+
}
494+
495+
// Reset batched state.
496+
this._liveResolverBatchRecordSource = null;
497+
this._handlingBatch = false;
498+
return shouldPublish;
499+
}
500+
}
501+
477502
_setLiveResolverValue(
478503
resolverRecord: Record,
479504
liveValue: LiveState<mixed>,

0 commit comments

Comments
 (0)