Skip to content

Commit

Permalink
Don't wrap sync work with scheduler in QueryExecutor
Browse files Browse the repository at this point in the history
Reviewed By: josephsavona

Differential Revision: D21401521

fbshipit-source-id: dc6207ca2c607d3a6e524ae5ee3b3c8f783c6113
  • Loading branch information
Juan Tejada authored and facebook-github-bot committed May 7, 2020
1 parent 37ae1e7 commit 8af3355
Show file tree
Hide file tree
Showing 5 changed files with 672 additions and 338 deletions.
20 changes: 8 additions & 12 deletions packages/relay-runtime/store/RelayModernQueryExecutor.js
Original file line number Diff line number Diff line change
Expand Up @@ -743,10 +743,8 @@ class Executor {
if (syncOperation != null) {
// If the operation module is available synchronously, normalize the
// data synchronously.
this._schedule(() => {
this._handleModuleImportPayload(moduleImportPayload, syncOperation);
this._maybeCompleteSubscriptionOperationTracking();
});
this._handleModuleImportPayload(moduleImportPayload, syncOperation);
this._maybeCompleteSubscriptionOperationTracking();
} else {
// Otherwise load the operation module and schedule a task to normalize
// the data when the module is available.
Expand Down Expand Up @@ -901,14 +899,12 @@ class Executor {
// If there were any queued responses, process them now that placeholders
// are in place
if (pendingResponses != null) {
this._schedule(() => {
const payloadFollowups = this._processIncrementalResponses(
pendingResponses,
);
const updatedOwners = this._publishQueue.run();
this._updateOperationTracker(updatedOwners);
this._processPayloadFollowups(payloadFollowups);
});
const payloadFollowups = this._processIncrementalResponses(
pendingResponses,
);
const updatedOwners = this._publishQueue.run();
this._updateOperationTracker(updatedOwners);
this._processPayloadFollowups(payloadFollowups);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,159 +218,199 @@ describe('execute() a query with @defer', () => {
});
});

it('processes deferred payloads with scheduling', () => {
let taskID = 0;
const tasks = new Map();
const scheduler = {
cancel: id => {
tasks.delete(id);
},
schedule: task => {
const id = String(taskID++);
tasks.set(id, task);
return id;
},
};
const runTask = () => {
for (const [id, task] of tasks) {
tasks.delete(id);
task();
break;
}
};
environment = new RelayModernEnvironment({
network: RelayNetwork.create(fetch),
scheduler,
store,
handlerProvider: name => {
switch (name) {
case 'name_handler':
return NameHandler;
describe('when using a scheduler', () => {
let taskID;
let tasks;
let scheduler;
let runTask;

beforeEach(() => {
taskID = 0;
tasks = new Map();
scheduler = {
cancel: id => {
tasks.delete(id);
},
schedule: task => {
const id = String(taskID++);
tasks.set(id, task);
return id;
},
};
runTask = () => {
for (const [id, task] of tasks) {
tasks.delete(id);
task();
break;
}
},
};
environment = new RelayModernEnvironment({
network: RelayNetwork.create(fetch),
scheduler,
store,
handlerProvider: name => {
switch (name) {
case 'name_handler':
return NameHandler;
}
},
});
});
const initialSnapshot = environment.lookup(selector);
const callback = jest.fn();
environment.subscribe(initialSnapshot, callback);

environment.execute({operation}).subscribe(callbacks);
dataSource.next({
data: {
node: {
it('processes deferred payloads with scheduling', () => {
const initialSnapshot = environment.lookup(selector);
const callback = jest.fn();
environment.subscribe(initialSnapshot, callback);

environment.execute({operation}).subscribe(callbacks);
dataSource.next({
data: {
node: {
id: '1',
__typename: 'User',
},
},
});
expect(next).toBeCalledTimes(0);
expect(callback).toBeCalledTimes(0);
expect(tasks.size).toBe(1);
runTask();
expect(tasks.size).toBe(0);
expect(next).toBeCalledTimes(1);
expect(callback).toBeCalledTimes(1);
jest.runAllTimers();
next.mockClear();
callback.mockClear();

dataSource.next({
data: {
id: '1',
__typename: 'User',
name: 'joe',
},
},
});
expect(next).toBeCalledTimes(0);
expect(callback).toBeCalledTimes(0);
expect(tasks.size).toBe(1);
runTask();
expect(next).toBeCalledTimes(1);
expect(callback).toBeCalledTimes(1);
jest.runAllTimers();
next.mockClear();
callback.mockClear();

dataSource.next({
data: {
label: 'UserQuery$defer$UserFragment',
path: ['node'],
});
expect(next).toBeCalledTimes(0);
expect(callback).toBeCalledTimes(0);
expect(tasks.size).toBe(1);
runTask();
expect(tasks.size).toBe(0);

expect(complete).toBeCalledTimes(0);
expect(error).toBeCalledTimes(0);
expect(next).toBeCalledTimes(1);
expect(callback).toBeCalledTimes(1);

const snapshot = callback.mock.calls[0][0];
expect(snapshot.isMissingData).toBe(false);
expect(snapshot.data).toEqual({
id: '1',
__typename: 'User',
name: 'joe',
},
label: 'UserQuery$defer$UserFragment',
path: ['node'],
name: 'JOE',
});
});
expect(next).toBeCalledTimes(0);
expect(callback).toBeCalledTimes(0);
expect(tasks.size).toBe(1);
runTask();

expect(complete).toBeCalledTimes(0);
expect(error).toBeCalledTimes(0);
expect(next).toBeCalledTimes(1);
expect(callback).toBeCalledTimes(1);
const snapshot = callback.mock.calls[0][0];
expect(snapshot.isMissingData).toBe(false);
expect(snapshot.data).toEqual({
id: '1',
name: 'JOE',
});
});
it('processes deferred payloads that are available synchronously within the same scheduler step', () => {
const initialSnapshot = environment.lookup(selector);
const callback = jest.fn();
environment.subscribe(initialSnapshot, callback);

it('cancels processing of deferred payloads with scheduling', () => {
let taskID = 0;
const tasks = new Map();
const scheduler = {
cancel: id => {
tasks.delete(id);
},
schedule: task => {
const id = String(taskID++);
tasks.set(id, task);
return id;
},
};
const runTask = () => {
for (const [id, task] of tasks) {
tasks.delete(id);
task();
break;
}
};
environment = new RelayModernEnvironment({
network: RelayNetwork.create(fetch),
scheduler,
store,
handlerProvider: name => {
switch (name) {
case 'name_handler':
return NameHandler;
}
},
environment.execute({operation}).subscribe(callbacks);
dataSource.next([
{
data: {
node: {
__typename: 'User',
id: '1',
name: 'joe',
},
},
},
{
data: {
id: '1',
__typename: 'User',
name: 'joe',
},
label: 'UserQuery$defer$UserFragment',
path: ['node'],
extensions: {
is_final: true,
},
},
]);
expect(complete).toBeCalledTimes(0);
expect(next).toBeCalledTimes(0);
expect(callback).toBeCalledTimes(0);
expect(tasks.size).toBe(1);

runTask();
expect(tasks.size).toBe(0);
expect(complete).toBeCalledTimes(0);
expect(next).toBeCalledTimes(1);
expect(callback).toBeCalledTimes(2);
jest.runAllTimers();

const snapshot1 = callback.mock.calls[0][0];
expect(snapshot1.isMissingData).toBe(true);
expect(snapshot1.data).toEqual({
id: '1',
});

const snapshot2 = callback.mock.calls[1][0];
expect(snapshot2.isMissingData).toBe(false);
expect(snapshot2.data).toEqual({
id: '1',
name: 'JOE',
});
});
const initialSnapshot = environment.lookup(selector);
const callback = jest.fn();
environment.subscribe(initialSnapshot, callback);

const subscription = environment.execute({operation}).subscribe(callbacks);
dataSource.next({
data: {
node: {
it('cancels processing of deferred payloads with scheduling', () => {
const initialSnapshot = environment.lookup(selector);
const callback = jest.fn();
environment.subscribe(initialSnapshot, callback);

const subscription = environment
.execute({operation})
.subscribe(callbacks);
dataSource.next({
data: {
node: {
id: '1',
__typename: 'User',
},
},
});
expect(next).toBeCalledTimes(0);
expect(callback).toBeCalledTimes(0);
expect(tasks.size).toBe(1);
runTask();
expect(next).toBeCalledTimes(1);
expect(callback).toBeCalledTimes(1);
jest.runAllTimers();
next.mockClear();
callback.mockClear();

dataSource.next({
data: {
id: '1',
__typename: 'User',
name: 'joe',
},
},
});
expect(next).toBeCalledTimes(0);
expect(callback).toBeCalledTimes(0);
expect(tasks.size).toBe(1);
runTask();
expect(next).toBeCalledTimes(1);
expect(callback).toBeCalledTimes(1);
jest.runAllTimers();
next.mockClear();
callback.mockClear();

dataSource.next({
data: {
id: '1',
__typename: 'User',
name: 'joe',
},
label: 'UserQuery$defer$UserFragment',
path: ['node'],
label: 'UserQuery$defer$UserFragment',
path: ['node'],
});
expect(next).toBeCalledTimes(0);
expect(callback).toBeCalledTimes(0);
expect(tasks.size).toBe(1);

subscription.unsubscribe();
expect(tasks.size).toBe(0);
expect(complete).toBeCalledTimes(0);
expect(error).toBeCalledTimes(0);
expect(next).toBeCalledTimes(0);
expect(callback).toBeCalledTimes(0);
});
expect(next).toBeCalledTimes(0);
expect(callback).toBeCalledTimes(0);
expect(tasks.size).toBe(1);

subscription.unsubscribe();
expect(tasks.size).toBe(0);
expect(complete).toBeCalledTimes(0);
expect(error).toBeCalledTimes(0);
expect(next).toBeCalledTimes(0);
expect(callback).toBeCalledTimes(0);
});

it('calls complete() when server completes after deferred payload resolves', () => {
Expand Down
Loading

0 comments on commit 8af3355

Please sign in to comment.