Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
/**
|
|
|
|
* Copyright 2004-present Facebook. All Rights Reserved.
|
|
|
|
*/
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
jest
|
2016-04-28 02:15:28 +00:00
|
|
|
.disableAutomock()
|
2016-03-02 12:27:13 +00:00
|
|
|
.mock('ErrorUtils')
|
2015-12-24 17:23:39 +00:00
|
|
|
.mock('BatchedBridge');
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
|
|
|
|
function expectToBeCalledOnce(fn) {
|
|
|
|
expect(fn.mock.calls.length).toBe(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
describe('InteractionManager', () => {
|
|
|
|
let InteractionManager;
|
|
|
|
let interactionStart;
|
|
|
|
let interactionComplete;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
jest.resetModuleRegistry();
|
|
|
|
InteractionManager = require('InteractionManager');
|
|
|
|
|
2016-04-28 02:15:28 +00:00
|
|
|
interactionStart = jest.fn();
|
|
|
|
interactionComplete = jest.fn();
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
|
|
|
|
InteractionManager.addListener(
|
|
|
|
InteractionManager.Events.interactionStart,
|
|
|
|
interactionStart
|
|
|
|
);
|
|
|
|
InteractionManager.addListener(
|
|
|
|
InteractionManager.Events.interactionComplete,
|
|
|
|
interactionComplete
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('throws when clearing an undefined handle', () => {
|
|
|
|
expect(() => InteractionManager.clearInteractionHandle()).toThrow();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('notifies asynchronously when interaction starts', () => {
|
|
|
|
InteractionManager.createInteractionHandle();
|
|
|
|
expect(interactionStart).not.toBeCalled();
|
|
|
|
|
|
|
|
jest.runAllTimers();
|
|
|
|
expect(interactionStart).toBeCalled();
|
|
|
|
expect(interactionComplete).not.toBeCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('notifies asynchronously when interaction stops', () => {
|
|
|
|
var handle = InteractionManager.createInteractionHandle();
|
|
|
|
jest.runAllTimers();
|
|
|
|
interactionStart.mockClear();
|
|
|
|
InteractionManager.clearInteractionHandle(handle);
|
|
|
|
expect(interactionComplete).not.toBeCalled();
|
|
|
|
|
|
|
|
jest.runAllTimers();
|
|
|
|
expect(interactionStart).not.toBeCalled();
|
|
|
|
expect(interactionComplete).toBeCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not notify when started & stopped in same event loop', () => {
|
|
|
|
var handle = InteractionManager.createInteractionHandle();
|
|
|
|
InteractionManager.clearInteractionHandle(handle);
|
|
|
|
|
|
|
|
jest.runAllTimers();
|
|
|
|
expect(interactionStart).not.toBeCalled();
|
|
|
|
expect(interactionComplete).not.toBeCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not notify when going from two -> one active interactions', () => {
|
|
|
|
InteractionManager.createInteractionHandle();
|
|
|
|
var handle = InteractionManager.createInteractionHandle();
|
|
|
|
jest.runAllTimers();
|
|
|
|
|
|
|
|
interactionStart.mockClear();
|
|
|
|
interactionComplete.mockClear();
|
|
|
|
|
|
|
|
InteractionManager.clearInteractionHandle(handle);
|
|
|
|
jest.runAllTimers();
|
|
|
|
expect(interactionStart).not.toBeCalled();
|
|
|
|
expect(interactionComplete).not.toBeCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('runs tasks asynchronously when there are interactions', () => {
|
2016-04-28 02:15:28 +00:00
|
|
|
var task = jest.fn();
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
InteractionManager.runAfterInteractions(task);
|
|
|
|
expect(task).not.toBeCalled();
|
|
|
|
|
|
|
|
jest.runAllTimers();
|
|
|
|
expect(task).toBeCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('runs tasks when interactions complete', () => {
|
2016-04-28 02:15:28 +00:00
|
|
|
var task = jest.fn();
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
var handle = InteractionManager.createInteractionHandle();
|
|
|
|
InteractionManager.runAfterInteractions(task);
|
|
|
|
|
|
|
|
jest.runAllTimers();
|
|
|
|
InteractionManager.clearInteractionHandle(handle);
|
|
|
|
expect(task).not.toBeCalled();
|
|
|
|
|
|
|
|
jest.runAllTimers();
|
|
|
|
expect(task).toBeCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not run tasks twice', () => {
|
2016-04-28 02:15:28 +00:00
|
|
|
var task1 = jest.fn();
|
|
|
|
var task2 = jest.fn();
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
InteractionManager.runAfterInteractions(task1);
|
|
|
|
jest.runAllTimers();
|
|
|
|
|
|
|
|
InteractionManager.runAfterInteractions(task2);
|
|
|
|
jest.runAllTimers();
|
|
|
|
|
|
|
|
expectToBeCalledOnce(task1);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('runs tasks added while processing previous tasks', () => {
|
2016-04-28 02:15:28 +00:00
|
|
|
var task1 = jest.fn(() => {
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
InteractionManager.runAfterInteractions(task2);
|
|
|
|
});
|
2016-04-28 02:15:28 +00:00
|
|
|
var task2 = jest.fn();
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
|
|
|
|
InteractionManager.runAfterInteractions(task1);
|
|
|
|
expect(task2).not.toBeCalled();
|
|
|
|
|
|
|
|
jest.runAllTimers();
|
|
|
|
|
|
|
|
expect(task1).toBeCalled();
|
|
|
|
expect(task2).toBeCalled();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('promise tasks', () => {
|
|
|
|
let InteractionManager;
|
|
|
|
let BatchedBridge;
|
|
|
|
let sequenceId;
|
|
|
|
function createSequenceTask(expectedSequenceId) {
|
2016-04-28 02:15:28 +00:00
|
|
|
return jest.fn(() => {
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
expect(++sequenceId).toBe(expectedSequenceId);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
beforeEach(() => {
|
|
|
|
jest.resetModuleRegistry();
|
|
|
|
InteractionManager = require('InteractionManager');
|
|
|
|
BatchedBridge = require('BatchedBridge');
|
|
|
|
sequenceId = 0;
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should run a basic promise task', () => {
|
2016-04-28 02:15:28 +00:00
|
|
|
const task1 = jest.fn(() => {
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
expect(++sequenceId).toBe(1);
|
|
|
|
return new Promise(resolve => resolve());
|
|
|
|
});
|
|
|
|
InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
|
|
|
|
jest.runAllTimers();
|
|
|
|
expectToBeCalledOnce(task1);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should handle nested promises', () => {
|
2016-04-28 02:15:28 +00:00
|
|
|
const task1 = jest.fn(() => {
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
expect(++sequenceId).toBe(1);
|
|
|
|
return new Promise(resolve => {
|
|
|
|
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'})
|
|
|
|
.then(resolve);
|
|
|
|
});
|
|
|
|
});
|
2016-04-28 02:15:28 +00:00
|
|
|
const task2 = jest.fn(() => {
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
expect(++sequenceId).toBe(2);
|
|
|
|
return new Promise(resolve => resolve());
|
|
|
|
});
|
|
|
|
InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
|
|
|
|
jest.runAllTimers();
|
|
|
|
expectToBeCalledOnce(task1);
|
|
|
|
expectToBeCalledOnce(task2);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should pause promise tasks during interactions then resume', () => {
|
|
|
|
const task1 = createSequenceTask(1);
|
2016-04-28 02:15:28 +00:00
|
|
|
const task2 = jest.fn(() => {
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
expect(++sequenceId).toBe(2);
|
|
|
|
return new Promise(resolve => {
|
|
|
|
setTimeout(() => {
|
|
|
|
InteractionManager.runAfterInteractions(task3).then(resolve);
|
|
|
|
}, 1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
const task3 = createSequenceTask(3);
|
|
|
|
InteractionManager.runAfterInteractions(task1);
|
|
|
|
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
|
|
|
|
jest.runOnlyPendingTimers();
|
|
|
|
expectToBeCalledOnce(task1);
|
|
|
|
expectToBeCalledOnce(task2);
|
|
|
|
const handle = InteractionManager.createInteractionHandle();
|
|
|
|
jest.runAllTimers();
|
|
|
|
jest.runAllTimers(); // Just to be sure...
|
|
|
|
expect(task3).not.toBeCalled();
|
|
|
|
InteractionManager.clearInteractionHandle(handle);
|
|
|
|
jest.runAllTimers();
|
|
|
|
expectToBeCalledOnce(task3);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should execute tasks in loop within deadline', () => {
|
|
|
|
InteractionManager.setDeadline(100);
|
|
|
|
BatchedBridge.getEventLoopRunningTime.mockReturnValue(10);
|
|
|
|
const task1 = createSequenceTask(1);
|
|
|
|
const task2 = createSequenceTask(2);
|
|
|
|
InteractionManager.runAfterInteractions(task1);
|
|
|
|
InteractionManager.runAfterInteractions(task2);
|
|
|
|
|
|
|
|
jest.runOnlyPendingTimers();
|
|
|
|
|
|
|
|
expectToBeCalledOnce(task1);
|
|
|
|
expectToBeCalledOnce(task2);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should execute tasks one at a time if deadline exceeded', () => {
|
|
|
|
InteractionManager.setDeadline(100);
|
|
|
|
BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
|
|
|
|
const task1 = createSequenceTask(1);
|
|
|
|
const task2 = createSequenceTask(2);
|
|
|
|
InteractionManager.runAfterInteractions(task1);
|
|
|
|
InteractionManager.runAfterInteractions(task2);
|
|
|
|
|
|
|
|
jest.runOnlyPendingTimers();
|
|
|
|
|
|
|
|
expectToBeCalledOnce(task1);
|
|
|
|
expect(task2).not.toBeCalled();
|
|
|
|
|
|
|
|
jest.runOnlyPendingTimers(); // resolve1
|
|
|
|
jest.runOnlyPendingTimers(); // task2
|
|
|
|
|
|
|
|
expectToBeCalledOnce(task2);
|
|
|
|
});
|
|
|
|
|
|
|
|
const bigAsyncTest = () => {
|
|
|
|
const task1 = createSequenceTask(1);
|
2016-04-28 02:15:28 +00:00
|
|
|
const task2 = jest.fn(() => {
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
expect(++sequenceId).toBe(2);
|
|
|
|
return new Promise(resolve => {
|
|
|
|
InteractionManager.runAfterInteractions(task3);
|
|
|
|
setTimeout(() => {
|
|
|
|
InteractionManager.runAfterInteractions({gen: task4, name: 'gen4'})
|
|
|
|
.then(resolve);
|
|
|
|
}, 1);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
const task3 = createSequenceTask(3);
|
2016-04-28 02:15:28 +00:00
|
|
|
const task4 = jest.fn(() => {
|
Add yieldy, chained async task support to InteractionManager
Summary:
Default behavior should be unchanged.
If we queue up a bunch of expensive tasks during an interaction, the default
`InteractionManager` behavior would execute them all in one synchronous loop at
the end the JS event loop via one `setImmediate` call, blocking the JS thread
the entire time.
The `setDeadline` addition in this diff enables an option to only execute tasks
until the `eventLoopRunningTime` is hit (added to MessageQueue/BatchedBridge),
allowing the queue execution to be paused if an interaction starts in between
tasks, making the app more responsive.
Additionally, if a task ends up generating a bunch of additional tasks
asynchronously, the previous implementation would execute these new tasks after
already scheduled tasks. This is often fine, but I want it to fully resolve
async tasks and all their dependencies before making progress in the rest of the
queue, so I added support for `type PromiseTask = {gen: () => Promise}` to do
just this. It works by building a stack of queues each time a `PromiseTask` is
started, and pops them off the stack once they are resolved and the queues are
processed.
I also pulled all of the actual queue logic out of `InteractionManager` and into
a new `TaskQueue` class to isolate concerns a bit.
public
Reviewed By: josephsavona
Differential Revision: D2754311
fb-gh-sync-id: bfd6d0c54e6410cb261aa1d2c5024dd91a3959e6
2015-12-24 00:10:39 +00:00
|
|
|
expect(++sequenceId).toBe(4);
|
|
|
|
return new Promise(resolve => {
|
|
|
|
InteractionManager.runAfterInteractions(task5).then(resolve);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
const task5 = createSequenceTask(5);
|
|
|
|
const task6 = createSequenceTask(6);
|
|
|
|
|
|
|
|
InteractionManager.runAfterInteractions(task1);
|
|
|
|
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
|
|
|
|
InteractionManager.runAfterInteractions(task6);
|
|
|
|
|
|
|
|
jest.runAllTimers();
|
|
|
|
// runAllTimers doesn't actually run all timers with nested timer functions
|
|
|
|
// inside Promises, so we have to call it extra times.
|
|
|
|
jest.runAllTimers();
|
|
|
|
jest.runAllTimers();
|
|
|
|
|
|
|
|
expectToBeCalledOnce(task1);
|
|
|
|
expectToBeCalledOnce(task2);
|
|
|
|
expectToBeCalledOnce(task3);
|
|
|
|
expectToBeCalledOnce(task4);
|
|
|
|
expectToBeCalledOnce(task5);
|
|
|
|
expectToBeCalledOnce(task6);
|
|
|
|
};
|
|
|
|
|
|
|
|
it('resolves async tasks recusively before other queued tasks', () => {
|
|
|
|
bigAsyncTest();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should also work with a deadline', () => {
|
|
|
|
InteractionManager.setDeadline(100);
|
|
|
|
BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
|
|
|
|
bigAsyncTest();
|
|
|
|
});
|
|
|
|
});
|